Skip to content

Commit ef7c65a

Browse files
hazzikfredericDelaporte
authored andcommitted
Add support for ElementAt/IList indexer for indexed collections in Linq queries
Fixes #819 Fixes #897
1 parent eda5fc9 commit ef7c65a

File tree

8 files changed

+334
-3
lines changed

8 files changed

+334
-3
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Linq;
12+
using System.Collections.Generic;
13+
using NHibernate.Cfg.MappingSchema;
14+
using NHibernate.Mapping.ByCode;
15+
using NUnit.Framework;
16+
using NHibernate.Linq;
17+
18+
namespace NHibernate.Test.NHSpecificTest.GH0819
19+
{
20+
using System.Threading.Tasks;
21+
[TestFixture]
22+
public class FixtureAsync : TestCaseMappingByCode
23+
{
24+
protected override HbmMapping GetMappings()
25+
{
26+
var mapper = new ModelMapper();
27+
mapper.Class<Parent>(rc =>
28+
{
29+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
30+
rc.Property(x => x.Name);
31+
rc.List(
32+
x => x.Children,
33+
m =>
34+
{
35+
m.Cascade(Mapping.ByCode.Cascade.All);
36+
m.Key(k => k.Column("ParentId"));
37+
m.Index(i => i.Column("Idx"));
38+
},
39+
r => r.OneToMany());
40+
});
41+
mapper.Class<Child>(
42+
rc =>
43+
{
44+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
45+
rc.Property(x => x.Name);
46+
rc.ManyToOne(x => x.Parent);
47+
});
48+
49+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
50+
}
51+
52+
protected override void OnSetUp()
53+
{
54+
using (var session = OpenSession())
55+
using (var transaction = session.BeginTransaction())
56+
{
57+
session.Save(new Parent
58+
{
59+
Name = "Bob",
60+
Children = new List<Child>
61+
{
62+
new Child {Name = "Fred"},
63+
new Child {Name = "Anna"},
64+
}
65+
});
66+
67+
var e2 = new Parent
68+
{
69+
Name = "Sally",
70+
Children = new List<Child>
71+
{
72+
new Child {Name = "Maria"},
73+
new Child {Name = "Theodor"},
74+
}
75+
};
76+
session.Save(e2);
77+
78+
transaction.Commit();
79+
}
80+
}
81+
82+
protected override void OnTearDown()
83+
{
84+
using (var session = OpenSession())
85+
using (var transaction = session.BeginTransaction())
86+
{
87+
session.CreateQuery("delete from Child").ExecuteUpdate();
88+
session.CreateQuery("delete from Parent").ExecuteUpdate();
89+
90+
transaction.Commit();
91+
}
92+
}
93+
94+
[Test]
95+
public async Task CanUseIndexerInQueryAsync()
96+
{
97+
using (var session = OpenSession())
98+
using (var transaction = session.BeginTransaction())
99+
{
100+
var result = await ((
101+
from e in session.Query<Parent>()
102+
where e.Children[0].Name == "Maria"
103+
select e
104+
).ToListAsync());
105+
106+
Assert.That(result, Has.Count.EqualTo(1));
107+
await (transaction.CommitAsync());
108+
}
109+
}
110+
111+
[Test]
112+
public async Task CanUseElementAtInQueryAsync()
113+
{
114+
using (var session = OpenSession())
115+
using (var transaction = session.BeginTransaction())
116+
{
117+
var result = await ((
118+
from e in session.Query<Parent>()
119+
where e.Children.ElementAt(0).Name == "Maria"
120+
select e
121+
).ToListAsync());
122+
123+
Assert.That(result, Has.Count.EqualTo(1));
124+
await (transaction.CommitAsync());
125+
}
126+
}
127+
}
128+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using System.Linq;
2+
using System.Collections.Generic;
3+
using NHibernate.Cfg.MappingSchema;
4+
using NHibernate.Mapping.ByCode;
5+
using NUnit.Framework;
6+
7+
namespace NHibernate.Test.NHSpecificTest.GH0819
8+
{
9+
[TestFixture]
10+
public class Fixture : TestCaseMappingByCode
11+
{
12+
protected override HbmMapping GetMappings()
13+
{
14+
var mapper = new ModelMapper();
15+
mapper.Class<Parent>(rc =>
16+
{
17+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
18+
rc.Property(x => x.Name);
19+
rc.List(
20+
x => x.Children,
21+
m =>
22+
{
23+
m.Cascade(Mapping.ByCode.Cascade.All);
24+
m.Key(k => k.Column("ParentId"));
25+
m.Index(i => i.Column("Idx"));
26+
},
27+
r => r.OneToMany());
28+
});
29+
mapper.Class<Child>(
30+
rc =>
31+
{
32+
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
33+
rc.Property(x => x.Name);
34+
rc.ManyToOne(x => x.Parent);
35+
});
36+
37+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
38+
}
39+
40+
protected override void OnSetUp()
41+
{
42+
using (var session = OpenSession())
43+
using (var transaction = session.BeginTransaction())
44+
{
45+
session.Save(new Parent
46+
{
47+
Name = "Bob",
48+
Children = new List<Child>
49+
{
50+
new Child {Name = "Fred"},
51+
new Child {Name = "Anna"},
52+
}
53+
});
54+
55+
var e2 = new Parent
56+
{
57+
Name = "Sally",
58+
Children = new List<Child>
59+
{
60+
new Child {Name = "Maria"},
61+
new Child {Name = "Theodor"},
62+
}
63+
};
64+
session.Save(e2);
65+
66+
transaction.Commit();
67+
}
68+
}
69+
70+
protected override void OnTearDown()
71+
{
72+
using (var session = OpenSession())
73+
using (var transaction = session.BeginTransaction())
74+
{
75+
session.CreateQuery("delete from Child").ExecuteUpdate();
76+
session.CreateQuery("delete from Parent").ExecuteUpdate();
77+
78+
transaction.Commit();
79+
}
80+
}
81+
82+
[Test]
83+
public void CanUseIndexerInQuery()
84+
{
85+
using (var session = OpenSession())
86+
using (var transaction = session.BeginTransaction())
87+
{
88+
var result = (
89+
from e in session.Query<Parent>()
90+
where e.Children[0].Name == "Maria"
91+
select e
92+
).ToList();
93+
94+
Assert.That(result, Has.Count.EqualTo(1));
95+
transaction.Commit();
96+
}
97+
}
98+
99+
[Test]
100+
public void CanUseElementAtInQuery()
101+
{
102+
using (var session = OpenSession())
103+
using (var transaction = session.BeginTransaction())
104+
{
105+
var result = (
106+
from e in session.Query<Parent>()
107+
where e.Children.ElementAt(0).Name == "Maria"
108+
select e
109+
).ToList();
110+
111+
Assert.That(result, Has.Count.EqualTo(1));
112+
transaction.Commit();
113+
}
114+
}
115+
}
116+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.GH0819
5+
{
6+
class Parent
7+
{
8+
public virtual Guid Id { get; set; }
9+
public virtual string Name { get; set; }
10+
11+
public virtual IList<Child> Children { get; set; }
12+
}
13+
14+
class Child
15+
{
16+
public virtual Guid Id { get; set; }
17+
18+
public virtual string Name { get; set; }
19+
20+
public virtual Parent Parent { get; set; }
21+
}
22+
}

src/NHibernate/Hql/Ast/HqlTreeBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,18 @@ public HqlTreeNode Coalesce(HqlExpression lhs, HqlExpression rhs)
497497
return new HqlCoalesce(_factory, lhs, rhs);
498498
}
499499

500+
//Since v5.2
501+
[Obsolete("Please use Index method instead.")]
500502
public HqlTreeNode DictionaryItem(HqlExpression dictionary, HqlExpression index)
501503
{
502504
return new HqlDictionaryIndex(_factory, dictionary, index);
503505
}
504506

507+
public HqlTreeNode Index(HqlExpression collection, HqlExpression index)
508+
{
509+
return new HqlIndex(_factory, collection, index);
510+
}
511+
505512
public HqlTreeNode Indices(HqlExpression dictionary)
506513
{
507514
return new HqlIndices(_factory, dictionary);

src/NHibernate/Hql/Ast/HqlTreeNode.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,10 +724,20 @@ public HqlCoalesce(IASTFactory factory, HqlExpression lhs, HqlExpression rhs)
724724
}
725725
}
726726

727-
public class HqlDictionaryIndex : HqlExpression
727+
public class HqlIndex : HqlExpression
728+
{
729+
public HqlIndex(IASTFactory factory, HqlExpression collection, HqlExpression index)
730+
: base(HqlSqlWalker.INDEX_OP, "[", factory, collection, index)
731+
{
732+
}
733+
}
734+
735+
//Since v5.2
736+
[Obsolete("Please use HqlIndex instead")]
737+
public class HqlDictionaryIndex : HqlIndex
728738
{
729739
public HqlDictionaryIndex(IASTFactory factory, HqlExpression dictionary, HqlExpression index)
730-
: base(HqlSqlWalker.INDEX_OP, "[", factory, dictionary, index)
740+
: base(factory, dictionary, index)
731741
{
732742
}
733743
}

src/NHibernate/Linq/Functions/DefaultLinqToHqlGeneratorsRegistry.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ public DefaultLinqToHqlGeneratorsRegistry()
6060
this.Merge(new DecimalNegateGenerator());
6161
this.Merge(new RoundGenerator());
6262
this.Merge(new TruncateGenerator());
63+
64+
var indexerGenerator = new ListIndexerGenerator();
65+
RegisterGenerator(indexerGenerator);
66+
this.Merge(indexerGenerator);
6367
}
6468

6569
protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator)

src/NHibernate/Linq/Functions/DictionaryGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject,
1919
{
2020
return treeBuilder.Dot(visitor.Visit(targetObject).AsExpression(), treeBuilder.Ident(memberName));
2121
}
22-
return treeBuilder.DictionaryItem(visitor.Visit(targetObject).AsExpression(), visitor.Visit(arguments[0]).AsExpression());
22+
return treeBuilder.Index(visitor.Visit(targetObject).AsExpression(), visitor.Visit(arguments[0]).AsExpression());
2323
}
2424
}
2525

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Linq;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
using NHibernate.Hql.Ast;
8+
using NHibernate.Linq.Visitors;
9+
using NHibernate.Util;
10+
11+
namespace NHibernate.Linq.Functions
12+
{
13+
internal class ListIndexerGenerator : BaseHqlGeneratorForMethod,IRuntimeMethodHqlGenerator
14+
{
15+
public ListIndexerGenerator()
16+
{
17+
SupportedMethods = new[]
18+
{
19+
ReflectHelper.GetMethodDefinition(() => Enumerable.ElementAt<object>(null, 0)),
20+
ReflectHelper.GetMethodDefinition(() => Queryable.ElementAt<object>(null, 0))
21+
};
22+
}
23+
24+
public bool SupportsMethod(MethodInfo method)
25+
{
26+
return method != null &&
27+
method.Name == "get_Item" &&
28+
(method.IsMethodOf(typeof(IList)) || method.IsMethodOf(typeof(IList<>)));
29+
}
30+
31+
public IHqlGeneratorForMethod GetMethodGenerator(MethodInfo method)
32+
{
33+
return this;
34+
}
35+
36+
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
37+
{
38+
var collection = visitor.Visit(method.IsStatic ? arguments[0] : targetObject).AsExpression();
39+
var index = visitor.Visit(method.IsStatic ? arguments[1] : arguments[0]).AsExpression();
40+
41+
return treeBuilder.Index(collection, index);
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)