Skip to content

Commit 74f9bb0

Browse files
committed
NH-2319 - Add ability to query collections with AsQueryable()
1 parent 12278f1 commit 74f9bb0

18 files changed

+618
-29
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
using System;
2+
using System.Linq;
3+
using NHibernate.Cfg;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Collection;
6+
using NHibernate.Mapping.ByCode;
7+
using NUnit.Framework;
8+
9+
namespace NHibernate.Test.NHSpecificTest.NH2319
10+
{
11+
public abstract class FixtureBase : TestCaseMappingByCode
12+
{
13+
private Guid parantId;
14+
private Guid child1Id;
15+
16+
[Test]
17+
public void ShouldBeAbleToFindChildrenByName()
18+
{
19+
using (var session = OpenSession())
20+
using (session.BeginTransaction())
21+
{
22+
var parent = session.Get<Parent>(parantId);
23+
24+
Assert.That(parent, Is.Not.Null);
25+
26+
var filtered = parent.Children
27+
.AsQueryable()
28+
.Where(x => x.Name == "Jack")
29+
.ToList();
30+
31+
Assert.That(filtered, Has.Count.EqualTo(1));
32+
Assert.That(filtered[0].Id, Is.EqualTo(child1Id));
33+
}
34+
}
35+
36+
[Test, Ignore("Does not work")]
37+
public void ShouldBeAbleToPerformComplexFiltering()
38+
{
39+
using (var session = OpenSession())
40+
using (session.BeginTransaction())
41+
{
42+
var parent = session.Get<Parent>(parantId);
43+
44+
Assert.NotNull(parent);
45+
46+
var filtered = parent.Children
47+
.AsQueryable()
48+
.Where(x => x.Name == "Piter")
49+
.SelectMany(x => x.GrandChildren)
50+
.Select(x => x.Id)
51+
//.ToList()
52+
.Count();
53+
54+
Assert.That(filtered, Is.EqualTo(2));
55+
}
56+
}
57+
58+
[Test]
59+
public void ShouldNotInitializeCollectionWhenPerformingQuery()
60+
{
61+
using (var session = OpenSession())
62+
using (session.BeginTransaction())
63+
{
64+
var parent = session.Get<Parent>(parantId);
65+
Assert.That(parent, Is.Not.Null);
66+
67+
var persistentCollection = (IPersistentCollection) parent.Children;
68+
69+
var filtered = parent.Children
70+
.AsQueryable()
71+
.Where(x => x.Name == "Jack")
72+
.ToList();
73+
74+
Assert.That(filtered, Has.Count.EqualTo(1));
75+
Assert.That(persistentCollection.WasInitialized, Is.False);
76+
}
77+
}
78+
79+
[Test]
80+
public void ShouldPerformSqlQueryEvenIfCollectionAlreadyInitialized()
81+
{
82+
using (var session = OpenSession())
83+
using (session.BeginTransaction())
84+
{
85+
var parent = session.Get<Parent>(parantId);
86+
Assert.That(parent, Is.Not.Null);
87+
88+
var loaded = parent.Children.ToList();
89+
Assert.That(loaded, Has.Count.EqualTo(2));
90+
91+
var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount;
92+
93+
var filtered = parent.Children
94+
.AsQueryable()
95+
.Where(x => x.Name == "Jack")
96+
.ToList();
97+
98+
var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount;
99+
100+
Assert.That(filtered, Has.Count.EqualTo(1));
101+
Assert.That(countAfterFiltering, Is.EqualTo(countBeforeFiltering + 1));
102+
}
103+
}
104+
105+
[Test]
106+
public void TestFilter()
107+
{
108+
using (var session = OpenSession())
109+
using (session.BeginTransaction())
110+
{
111+
var parent = session.Get<Parent>(parantId);
112+
Assert.That(parent, Is.Not.Null);
113+
114+
var children = session.CreateFilter(parent.Children, "where this.Name = 'Jack'")
115+
.List<Child>();
116+
117+
Assert.That(children, Has.Count.EqualTo(1));
118+
}
119+
}
120+
121+
protected override void Configure(Configuration configuration)
122+
{
123+
configuration.SetProperty("generate_statistics", "true");
124+
}
125+
126+
protected override void OnSetUp()
127+
{
128+
using (var session = OpenSession())
129+
using (var transaction = session.BeginTransaction())
130+
{
131+
var parent1 = new Parent {Name = "Bob"};
132+
parantId = (Guid) session.Save(parent1);
133+
134+
var parent2 = new Parent {Name = "Martin"};
135+
session.Save(parent2);
136+
137+
var child1 = new Child
138+
{
139+
Name = "Jack",
140+
Parent = parent1
141+
};
142+
child1Id = (Guid) session.Save(child1);
143+
144+
var child2 = new Child
145+
{
146+
Name = "Piter",
147+
Parent = parent1
148+
};
149+
session.Save(child2);
150+
151+
var grandChild1 = new GrandChild
152+
{
153+
Name = "Kate",
154+
Child = child2
155+
};
156+
session.Save(grandChild1);
157+
158+
var grandChild2 = new GrandChild
159+
{
160+
Name = "Mary",
161+
Child = child2
162+
};
163+
session.Save(grandChild2);
164+
165+
var child3 = new Child
166+
{
167+
Name = "Jack",
168+
Parent = parent2
169+
};
170+
session.Save(child3);
171+
172+
session.Flush();
173+
transaction.Commit();
174+
}
175+
}
176+
177+
protected override void OnTearDown()
178+
{
179+
using (var session = OpenSession())
180+
using (var transaction = session.BeginTransaction())
181+
{
182+
session.Delete("from System.Object");
183+
184+
session.Flush();
185+
transaction.Commit();
186+
}
187+
}
188+
}
189+
190+
[TestFixture]
191+
public class BagFixture : FixtureBase
192+
{
193+
protected override HbmMapping GetMappings()
194+
{
195+
var mapper = new ModelMapper();
196+
mapper.Class<Parent>(rc =>
197+
{
198+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
199+
rc.Property(x => x.Name);
200+
rc.Bag(x => x.Children,
201+
map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans),
202+
rel => rel.OneToMany());
203+
});
204+
205+
mapper.Class<Child>(rc =>
206+
{
207+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
208+
rc.Property(x => x.Name);
209+
rc.ManyToOne(x => x.Parent);
210+
rc.Bag(x => x.GrandChildren, map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans), rel => rel.OneToMany());
211+
});
212+
213+
mapper.Class<GrandChild>(rc =>
214+
{
215+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
216+
rc.Property(x => x.Name);
217+
rc.ManyToOne(x => x.Child, x => x.Column("child_id"));
218+
});
219+
220+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
221+
}
222+
223+
}
224+
225+
[TestFixture]
226+
public class SetFixture : FixtureBase
227+
{
228+
protected override HbmMapping GetMappings()
229+
{
230+
var mapper = new ModelMapper();
231+
mapper.Class<Parent>(rc =>
232+
{
233+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
234+
rc.Property(x => x.Name);
235+
rc.Set(x => x.Children,
236+
map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans),
237+
rel => rel.OneToMany());
238+
});
239+
240+
mapper.Class<Child>(rc =>
241+
{
242+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
243+
rc.Property(x => x.Name);
244+
rc.ManyToOne(x => x.Parent);
245+
rc.Set(x => x.GrandChildren,
246+
map => map.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans),
247+
rel => rel.OneToMany());
248+
});
249+
250+
mapper.Class<GrandChild>(rc =>
251+
{
252+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
253+
rc.Property(x => x.Name);
254+
rc.ManyToOne(x => x.Child, x => x.Column("child_id"));
255+
});
256+
257+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
258+
}
259+
}
260+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH2319
5+
{
6+
class Parent
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual string Name { get; set; }
10+
public virtual ICollection<Child> Children { get; set; }
11+
}
12+
13+
class Child
14+
{
15+
public virtual Parent Parent { get; set; }
16+
public virtual Guid Id { get; set; }
17+
public virtual string Name { get; set; }
18+
public virtual ICollection<Child> GrandChildren { get; set; }
19+
}
20+
class GrandChild
21+
{
22+
public virtual Child Child { get; set; }
23+
public virtual Guid Id { get; set; }
24+
public virtual string Name { get; set; }
25+
public virtual ICollection<GrandChild> Children { get; set; }
26+
}
27+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,8 @@
730730
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Domain.cs" />
731731
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Fixture.cs" />
732732
<Compile Include="Linq\ByMethod\DistinctTests.cs" />
733+
<Compile Include="NHSpecificTest\NH2319\Model.cs" />
734+
<Compile Include="NHSpecificTest\NH2319\Fixture.cs" />
733735
<Compile Include="Component\Basic\ComponentWithUniqueConstraintTests.cs" />
734736
<Compile Include="NHSpecificTest\EntityWithUserTypeCanHaveLinqGenerators\BarExample.cs" />
735737
<Compile Include="NHSpecificTest\EntityWithUserTypeCanHaveLinqGenerators\DoubleStringUserType.cs" />

src/NHibernate/Collection/AbstractPersistentCollection.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Data.Common;
5+
using System.Linq;
6+
using System.Linq.Expressions;
57
using NHibernate.Collection.Generic;
68
using NHibernate.Engine;
79
using NHibernate.Impl;
10+
using NHibernate.Linq;
811
using NHibernate.Loader;
912
using NHibernate.Persister.Collection;
1013
using NHibernate.Type;
@@ -80,7 +83,7 @@ public void Reset()
8083
}
8184
}
8285

83-
[NonSerialized] private ISessionImplementor session;
86+
[NonSerialized] protected ISessionImplementor session;
8487
private bool initialized;
8588
[NonSerialized] private List<IDelayedOperation> operationQueue;
8689
[NonSerialized] private bool directlyAccessible;

src/NHibernate/Collection/Generic/PersistentGenericBag.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
using System.Collections.Generic;
44
using System.Data.Common;
55
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Linq.Expressions;
68
using NHibernate.DebugHelpers;
79
using NHibernate.Engine;
10+
using NHibernate.Linq;
811
using NHibernate.Loader;
912
using NHibernate.Persister.Collection;
1013
using NHibernate.Type;
@@ -22,7 +25,7 @@ namespace NHibernate.Collection.Generic
2225
/// <remarks>The underlying collection used is an <see cref="List{T}"/></remarks>
2326
[Serializable]
2427
[DebuggerTypeProxy(typeof (CollectionProxy<>))]
25-
public class PersistentGenericBag<T> : AbstractPersistentCollection, IList<T>, IList
28+
public class PersistentGenericBag<T> : AbstractPersistentCollection, IList<T>, IList, IQueryable<T>
2629
{
2730
// TODO NH: find a way to writeonce (no duplicated code from PersistentBag)
2831

@@ -46,6 +49,8 @@ public class PersistentGenericBag<T> : AbstractPersistentCollection, IList<T>, I
4649
*/
4750
private IList<T> _gbag;
4851

52+
private IQueryable<T> queryable;
53+
4954
public PersistentGenericBag()
5055
{
5156
}
@@ -162,6 +167,26 @@ public bool IsReadOnly
162167
get { return false; }
163168
}
164169

170+
public Expression Expression
171+
{
172+
get { return InnerQueryable.Expression; }
173+
}
174+
175+
public System.Type ElementType
176+
{
177+
get { return InnerQueryable.ElementType; }
178+
}
179+
180+
public IQueryProvider Provider
181+
{
182+
get { return InnerQueryable.Provider; }
183+
}
184+
185+
private IQueryable<T> InnerQueryable
186+
{
187+
get { return queryable ?? (queryable = new NhQueryable<T>(Session, this)); }
188+
}
189+
165190
public void Add(T item)
166191
{
167192
if (!IsOperationQueueEnabled)

0 commit comments

Comments
 (0)