Skip to content

Commit 901f468

Browse files
committed
NH-2319 - Add ability to query collections with AsQueryable()
1 parent fac75a2 commit 901f468

18 files changed

+648
-56
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
namespace NHibernate.Test.NHSpecificTest.NH2981
2+
{
3+
using System;
4+
using System.Linq;
5+
using Cfg;
6+
using Cfg.MappingSchema;
7+
using Collection;
8+
using Mapping.ByCode;
9+
using NUnit.Framework;
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.NotNull(parent);
25+
26+
var filtered = parent.Children
27+
.AsQueryable()
28+
.Where(x => x.Name == "Jack")
29+
.ToList();
30+
31+
Assert.AreEqual(1, filtered.Count);
32+
Assert.AreEqual(child1Id, filtered[0].Id);
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.AreEqual(2, filtered);
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.NotNull(parent);
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.AreEqual(1, filtered.Count);
75+
Assert.False(persistentCollection.WasInitialized);
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+
87+
Assert.NotNull(parent);
88+
89+
var loaded = parent.Children.ToList();
90+
Assert.AreEqual(2, loaded.Count);
91+
92+
var countBeforeFiltering = session.SessionFactory.Statistics.QueryExecutionCount;
93+
94+
var filtered = parent.Children
95+
.AsQueryable()
96+
.Where(x => x.Name == "Jack")
97+
.ToList();
98+
99+
var countAfterFiltering = session.SessionFactory.Statistics.QueryExecutionCount;
100+
101+
Assert.AreEqual(1, filtered.Count);
102+
Assert.AreEqual(countBeforeFiltering + 1, countAfterFiltering);
103+
}
104+
}
105+
106+
[Test]
107+
public void TestFilter()
108+
{
109+
using (var session = OpenSession())
110+
using (session.BeginTransaction())
111+
{
112+
var parent = session.Get<Parent>(parantId);
113+
114+
Assert.NotNull(parent);
115+
116+
var children = session.CreateFilter(parent.Children, "where this.Name = 'Jack'")
117+
.List<Child>();
118+
119+
Assert.AreEqual(1, children.Count);
120+
}
121+
}
122+
123+
protected override void Configure(Configuration configuration)
124+
{
125+
configuration.SetProperty("show_sql", "true");
126+
configuration.SetProperty("generate_statistics", "true");
127+
base.Configure(configuration);
128+
}
129+
130+
protected override void OnSetUp()
131+
{
132+
using (var session = OpenSession())
133+
using (var transaction = session.BeginTransaction())
134+
{
135+
var parent1 = new Parent {Name = "Bob"};
136+
parantId = (Guid) session.Save(parent1);
137+
138+
var parent2 = new Parent {Name = "Martin"};
139+
session.Save(parent2);
140+
141+
var child1 = new Child
142+
{
143+
Name = "Jack",
144+
Parent = parent1
145+
};
146+
child1Id = (Guid) session.Save(child1);
147+
148+
var child2 = new Child
149+
{
150+
Name = "Piter",
151+
Parent = parent1
152+
};
153+
session.Save(child2);
154+
155+
var grandChild1 = new GrandChild
156+
{
157+
Name = "Kate",
158+
Child = child2
159+
};
160+
session.Save(grandChild1);
161+
162+
var grandChild2 = new GrandChild
163+
{
164+
Name = "Mary",
165+
Child = child2
166+
};
167+
session.Save(grandChild2);
168+
169+
var child3 = new Child
170+
{
171+
Name = "Jack",
172+
Parent = parent2
173+
};
174+
session.Save(child3);
175+
176+
session.Flush();
177+
transaction.Commit();
178+
}
179+
}
180+
181+
protected override void OnTearDown()
182+
{
183+
using (var session = OpenSession())
184+
using (var transaction = session.BeginTransaction())
185+
{
186+
session.Delete("from System.Object");
187+
188+
session.Flush();
189+
transaction.Commit();
190+
}
191+
}
192+
193+
}
194+
195+
public class BagFixture : FixtureBase
196+
{
197+
protected override HbmMapping GetMappings()
198+
{
199+
var mapper = new ModelMapper();
200+
mapper.Class<Parent>(rc =>
201+
{
202+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
203+
rc.Property(x => x.Name);
204+
rc.Bag(x => x.Children,
205+
map => map.Cascade(Cascade.All | Cascade.DeleteOrphans),
206+
rel => rel.OneToMany());
207+
});
208+
209+
mapper.Class<Child>(rc =>
210+
{
211+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
212+
rc.Property(x => x.Name);
213+
rc.ManyToOne(x => x.Parent);
214+
rc.Bag(x => x.GrandChildren, map => map.Cascade(Cascade.All | Cascade.DeleteOrphans), rel => rel.OneToMany());
215+
});
216+
217+
mapper.Class<GrandChild>(rc =>
218+
{
219+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
220+
rc.Property(x => x.Name);
221+
rc.ManyToOne(x => x.Child, x => x.Column("child_id"));
222+
});
223+
224+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
225+
}
226+
227+
}
228+
229+
public class SetFixture : FixtureBase
230+
{
231+
protected override HbmMapping GetMappings()
232+
{
233+
var mapper = new ModelMapper();
234+
mapper.Class<Parent>(rc =>
235+
{
236+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
237+
rc.Property(x => x.Name);
238+
rc.Set(x => x.Children,
239+
map => map.Cascade(Cascade.All | Cascade.DeleteOrphans),
240+
rel => rel.OneToMany());
241+
});
242+
243+
mapper.Class<Child>(rc =>
244+
{
245+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
246+
rc.Property(x => x.Name);
247+
rc.ManyToOne(x => x.Parent);
248+
rc.Set(x => x.GrandChildren,
249+
map => map.Cascade(Cascade.All | Cascade.DeleteOrphans),
250+
rel => rel.OneToMany());
251+
});
252+
253+
mapper.Class<GrandChild>(rc =>
254+
{
255+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
256+
rc.Property(x => x.Name);
257+
rc.ManyToOne(x => x.Child, x => x.Column("child_id"));
258+
});
259+
260+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
261+
}
262+
}
263+
}
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.NH2981
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
@@ -690,6 +690,8 @@
690690
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Domain.cs" />
691691
<Compile Include="NHSpecificTest\BagWithLazyExtraAndFilter\Fixture.cs" />
692692
<Compile Include="Linq\ByMethod\DistinctTests.cs" />
693+
<Compile Include="NHSpecificTest\NH2319\Parent.cs" />
694+
<Compile Include="NHSpecificTest\NH2319\FixtureBase.cs" />
693695
<Compile Include="Component\Basic\ComponentWithUniqueConstraintTests.cs" />
694696
<Compile Include="NHSpecificTest\NH3487\Entity.cs" />
695697
<Compile Include="NHSpecificTest\NH3487\Fixture.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;
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;
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

@@ -45,6 +48,8 @@ public class PersistentGenericBag<T> : AbstractPersistentCollection, IList<T>, I
4548
/// <one-to-many> <bag>!
4649
private IList<T> _gbag;
4750

51+
private IQueryable<T> queryable;
52+
4853
public PersistentGenericBag()
4954
{
5055
}
@@ -161,6 +166,26 @@ public bool IsReadOnly
161166
get { return false; }
162167
}
163168

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

0 commit comments

Comments
 (0)