You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2021/01/12 10:54:13 UTC
[ignite] branch master updated: IGNITE-13754 .NET: Fix LINQ
provider for queries with JOIN and GROUP BY combined
This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 634ac6d IGNITE-13754 .NET: Fix LINQ provider for queries with JOIN and GROUP BY combined
634ac6d is described below
commit 634ac6dfe36c4946abd6049423bc65bff8907c17
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Jan 12 13:53:40 2021 +0300
IGNITE-13754 .NET: Fix LINQ provider for queries with JOIN and GROUP BY combined
Fix projection traversal in `AliasDictionary.GetQuerySource` and `CacheQueryExpressionVisitor.VisitMember`: use `memberHint` to understand where the given member of the projected type comes from to emit correct table and field name.
---
.../Apache.Ignite.Core.Tests.csproj | 1 +
.../Cache/Query/Linq/CacheLinqTest.Functions.cs | 98 ---------
...qTest.Functions.cs => CacheLinqTest.GroupBy.cs} | 222 ++++++++++++---------
.../Log/CustomLoggerTest.cs | 9 +-
.../Apache.Ignite.Linq/Impl/AliasDictionary.cs | 43 +---
.../Impl/CacheQueryExpressionVisitor.cs | 5 +-
.../Apache.Ignite.Linq/Impl/ExpressionWalker.cs | 130 +++++++++++-
7 files changed, 270 insertions(+), 238 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index e7fb47c..01c7e65 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -137,6 +137,7 @@
<Compile Include="Cache\Query\Linq\CacheLinqTest.CompiledQuery.cs" />
<Compile Include="Cache\Query\Linq\CacheLinqTest.DateTime.cs" />
<Compile Include="Cache\Query\Linq\CacheLinqTest.Aggregates.cs" />
+ <Compile Include="Cache\Query\Linq\CacheLinqTest.GroupBy.cs" />
<Compile Include="Cache\Query\Linq\CacheLinqTest.Join.cs" />
<Compile Include="Cache\Query\Linq\CacheLinqTest.Join.LocalCollection.cs" />
<Compile Include="Cache\Query\Linq\CacheLinqTest.Strings.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
index dd6b2ce..7020504 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
@@ -61,104 +61,6 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
}
/// <summary>
- /// Tests the group by.
- /// </summary>
- [Test]
- public void TestGroupBy()
- {
- var persons = GetPersonCache().AsCacheQueryable();
- var orgs = GetOrgCache().AsCacheQueryable();
-
- // Simple, unordered
- CollectionAssert.AreEquivalent(new[] { 1000, 1001 },
- persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).ToArray());
-
- // Aggregate
- Assert.AreEqual(1000,
- persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).OrderBy(x => x).First());
-
- // Ordering and count
- var res1 =
- from p in persons
- orderby p.Value.Name
- group p by p.Value.OrganizationId
- into gs
- orderby gs.Key
- where gs.Count() > 10
- select new { Count = gs.Count(), OrgId = gs.Key, AvgAge = gs.Average(x => x.Value.Age) };
-
- var resArr = res1.ToArray();
-
- Assert.AreEqual(new[]
- {
- new {Count = PersonCount/2, OrgId = 1000, AvgAge = (double) PersonCount/2 - 1},
- new {Count = PersonCount/2, OrgId = 1001, AvgAge = (double) PersonCount/2}
- }, resArr);
-
- // Join and sum
- var res2 = persons.Join(orgs.Where(o => o.Key > 10), p => p.Value.OrganizationId, o => o.Key,
- (p, o) => new { p, o })
- .GroupBy(x => x.o.Value.Name)
- .Select(g => new { Org = g.Key, AgeSum = g.Select(x => x.p.Value.Age).Sum() });
-
- var resArr2 = res2.ToArray();
-
- Assert.AreEqual(new[]
- {
- new {Org = "Org_0", AgeSum = persons.Where(x => x.Value.OrganizationId == 1000).Sum(x => x.Value.Age)},
- new {Org = "Org_1", AgeSum = persons.Where(x => x.Value.OrganizationId == 1001).Sum(x => x.Value.Age)}
- }, resArr2);
- }
-
- /// <summary>
- /// Tests the GroupBy with Where subquery.
- /// </summary>
- [Test]
- public void TestGroupBySubquery()
- {
- var persons = GetPersonCache().AsCacheQueryable().Where(p => p.Value.OrganizationId == 1000);
- var orgs = GetOrgCache().AsCacheQueryable();
-
- // Simple, unordered
- CollectionAssert.AreEquivalent(new[] { 1000 },
- persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).ToArray());
-
- // Aggregate
- Assert.AreEqual(1000,
- persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).OrderBy(x => x).First());
-
- // Ordering and count
- var res1 =
- from p in persons
- orderby p.Value.Name
- group p by p.Value.OrganizationId
- into gs
- orderby gs.Key
- where gs.Count() > 10
- select new { Count = gs.Count(), OrgId = gs.Key, AvgAge = gs.Average(x => x.Value.Age) };
-
- var resArr = res1.ToArray();
-
- Assert.AreEqual(new[]
- {
- new {Count = PersonCount/2, OrgId = 1000, AvgAge = (double) PersonCount/2 - 1},
- }, resArr);
-
- // Join and sum
- var res2 = persons.Join(orgs.Where(o => o.Key > 10), p => p.Value.OrganizationId, o => o.Key,
- (p, o) => new { p, o })
- .GroupBy(x => x.o.Value.Name)
- .Select(g => new { Org = g.Key, AgeSum = g.Select(x => x.p.Value.Age).Sum() });
-
- var resArr2 = res2.ToArray();
-
- Assert.AreEqual(new[]
- {
- new {Org = "Org_0", AgeSum = persons.Where(x => x.Value.OrganizationId == 1000).Sum(x => x.Value.Age)},
- }, resArr2);
- }
-
- /// <summary>
/// Tests the union.
/// </summary>
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.GroupBy.cs
similarity index 50%
copy from modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
copy to modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.GroupBy.cs
index dd6b2ce..94233a1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.GroupBy.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -15,15 +15,6 @@
* limitations under the License.
*/
-// ReSharper disable SuspiciousTypeConversion.Global
-// ReSharper disable MemberCanBePrivate.Global
-// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
-// ReSharper disable UnusedAutoPropertyAccessor.Global
-// ReSharper disable StringIndexOfIsCultureSpecific.1
-// ReSharper disable StringIndexOfIsCultureSpecific.2
-// ReSharper disable StringCompareToIsCultureSpecific
-// ReSharper disable StringCompareIsCultureSpecific.1
-// ReSharper disable UnusedMemberInSuper.Global
namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
{
using System.Linq;
@@ -31,37 +22,12 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
using NUnit.Framework;
/// <summary>
- /// Tests LINQ.
+ /// GroupBy tests.
/// </summary>
public partial class CacheLinqTest
{
/// <summary>
- /// Tests where clause.
- /// </summary>
- [Test]
- public void TestWhere()
- {
- var cache = GetPersonCache().AsCacheQueryable();
-
- // Test const and var parameters
- const int age = 10;
- var key = 15;
-
- Assert.AreEqual(age, cache.Where(x => x.Value.Age < age).ToArray().Length);
- Assert.AreEqual(age, cache.Where(x => x.Value.Address.Zip < age).ToArray().Length);
- Assert.AreEqual(19, cache.Where(x => x.Value.Age > age && x.Value.Age < 30).ToArray().Length);
- Assert.AreEqual(20, cache.Where(x => x.Value.Age > age).Count(x => x.Value.Age < 30 || x.Value.Age == 50));
- Assert.AreEqual(key, cache.Where(x => x.Key < key).ToArray().Length);
- Assert.AreEqual(key, cache.Where(x => -x.Key > -key).ToArray().Length);
-
- Assert.AreEqual(1, GetRoleCache().AsCacheQueryable().Where(x => x.Key.Foo < 2).ToArray().Length);
- var cacheEntries = GetRoleCache().AsCacheQueryable().Where(x => x.Key.Bar > 2 && x.Value.Name != "11")
- .ToArray();
- Assert.AreEqual(3, cacheEntries.Length);
- }
-
- /// <summary>
- /// Tests the group by.
+ /// Tests grouping.
/// </summary>
[Test]
public void TestGroupBy()
@@ -111,10 +77,10 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
}
/// <summary>
- /// Tests the GroupBy with Where subquery.
+ /// Tests the GroupBy with Where sub-query.
/// </summary>
[Test]
- public void TestGroupBySubquery()
+ public void TestGroupBySubQuery()
{
var persons = GetPersonCache().AsCacheQueryable().Where(p => p.Value.OrganizationId == 1000);
var orgs = GetOrgCache().AsCacheQueryable();
@@ -159,102 +125,170 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
}
/// <summary>
- /// Tests the union.
+ /// Tests grouping combined with join.
/// </summary>
[Test]
- public void TestUnion()
+ public void TestGroupByWithJoin()
{
- // Direct union
+ var organizations = GetOrgCache().AsCacheQueryable();
var persons = GetPersonCache().AsCacheQueryable();
- var persons2 = GetSecondPersonCache().AsCacheQueryable();
- var res = persons.Union(persons2).ToArray();
+ var qry = persons.Join(
+ organizations,
+ p => p.Value.OrganizationId,
+ o => o.Value.Id,
+ (person, org) => new {person, org})
+ .GroupBy(x => x.person.Value.OrganizationId)
+ .Select(g => new {OrgId = g.Key, MaxAge = g.Max(x => x.person.Value.Age)})
+ .OrderBy(x => x.MaxAge);
- Assert.AreEqual(PersonCount * 2, res.Length);
+ var res = qry.ToArray();
- // Subquery
- var roles = GetRoleCache().AsCacheQueryable().Select(x => -x.Key.Foo);
- var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Union(roles).ToArray();
+ Assert.AreEqual(2, res.Length);
- Assert.AreEqual(RoleCount + PersonCount, ids.Length);
+ Assert.AreEqual(1000, res[0].OrgId);
+ Assert.AreEqual(898, res[0].MaxAge);
+
+ Assert.AreEqual(1001, res[1].OrgId);
+ Assert.AreEqual(899, res[1].MaxAge);
}
/// <summary>
- /// Tests intersect.
+ /// Tests grouping combined with join in a reverse order.
/// </summary>
[Test]
- public void TestIntersect()
+ public void TestGroupByWithReverseJoin()
{
- // Direct intersect
+ var organizations = GetOrgCache().AsCacheQueryable();
var persons = GetPersonCache().AsCacheQueryable();
- var persons2 = GetSecondPersonCache().AsCacheQueryable();
- var res = persons.Intersect(persons2).ToArray();
+ var qry = organizations.Join(
+ persons,
+ o => o.Value.Id,
+ p => p.Value.OrganizationId,
+ (org, person) => new {person, org})
+ .GroupBy(x => x.person.Value.OrganizationId)
+ .Select(g =>
+ new {OrgId = g.Key, MaxAge = g.Max(x => x.person.Value.Age)})
+ .OrderBy(x => x.MaxAge);
- Assert.AreEqual(0, res.Length);
+ var res = qry.ToArray();
- // Subquery
- var roles = GetRoleCache().AsCacheQueryable().Select(x => x.Key.Foo);
- var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Intersect(roles).ToArray();
+ Assert.AreEqual(2, res.Length);
- Assert.AreEqual(RoleCount, ids.Length);
- }
+ Assert.AreEqual(1000, res[0].OrgId);
+ Assert.AreEqual(898, res[0].MaxAge);
+ Assert.AreEqual(1001, res[1].OrgId);
+ Assert.AreEqual(899, res[1].MaxAge);
+ }
+
/// <summary>
- /// Tests except.
+ /// Tests grouping combined with join in a reverse order followed by a projection to an anonymous type.
/// </summary>
[Test]
- public void TestExcept()
+ public void TestGroupByWithReverseJoinAndProjection()
{
- // Direct except
+ var organizations = GetOrgCache().AsCacheQueryable();
var persons = GetPersonCache().AsCacheQueryable();
- var persons2 = GetSecondPersonCache().AsCacheQueryable();
- var res = persons.Except(persons2).ToArray();
+ var qry = organizations.Join(
+ persons,
+ o => o.Value.Id,
+ p => p.Value.OrganizationId,
+ (org, person) => new
+ {
+ OrgName = org.Value.Name,
+ person.Value.Age
+ })
+ .GroupBy(x => x.OrgName)
+ .Select(g => new {OrgId = g.Key, MaxAge = g.Max(x => x.Age)})
+ .OrderBy(x => x.MaxAge);
- Assert.AreEqual(PersonCount, res.Length);
+ var res = qry.ToArray();
- // Subquery
- var roles = GetRoleCache().AsCacheQueryable().Select(x => x.Key.Foo);
- var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Except(roles).ToArray();
+ Assert.AreEqual(2, res.Length);
- Assert.AreEqual(PersonCount - RoleCount, ids.Length);
- }
+ Assert.AreEqual("Org_0", res[0].OrgId);
+ Assert.AreEqual(898, res[0].MaxAge);
+ Assert.AreEqual("Org_1", res[1].OrgId);
+ Assert.AreEqual(899, res[1].MaxAge);
+ }
+
/// <summary>
- /// Tests ordering.
+ /// Tests grouping combined with join in a reverse order followed by a projection to an anonymous type with
+ /// custom projected column names.
/// </summary>
[Test]
- public void TestOrdering()
+ public void TestGroupByWithReverseJoinAndProjectionWithRename()
{
- var persons = GetPersonCache().AsCacheQueryable()
- .OrderByDescending(x => x.Key)
- .ThenBy(x => x.Value.Age)
- .ToArray();
+ var organizations = GetOrgCache().AsCacheQueryable();
+ var persons = GetPersonCache().AsCacheQueryable();
- Assert.AreEqual(Enumerable.Range(0, PersonCount).Reverse().ToArray(),
- persons.Select(x => x.Key).ToArray());
+ var qry = organizations.Join(
+ persons,
+ o => o.Value.Id,
+ p => p.Value.OrganizationId,
+ (org, person) => new Projection
+ {
+ OrgName = org.Value.Name,
+ PersonAge = person.Value.Age
+ })
+ .GroupBy(x => x.OrgName)
+ .Select(g => new {OrgId = g.Key, MaxAge = g.Max(x => x.PersonAge)})
+ .OrderBy(x => x.MaxAge);
+
+ var res = qry.ToArray();
+
+ Assert.AreEqual(2, res.Length);
+
+ Assert.AreEqual("Org_0", res[0].OrgId);
+ Assert.AreEqual(898, res[0].MaxAge);
+
+ Assert.AreEqual("Org_1", res[1].OrgId);
+ Assert.AreEqual(899, res[1].MaxAge);
+ }
+
+ /// <summary>
+ /// Tests grouping combined with join in a reverse order followed by a projection to an anonymous type with
+ /// custom projected column names.
+ /// </summary>
+ [Test]
+ public void TestGroupByWithReverseJoinAndAnonymousProjectionWithRename()
+ {
+ var organizations = GetOrgCache().AsCacheQueryable();
+ var persons = GetPersonCache().AsCacheQueryable();
- var personsByOrg = GetPersonCache().AsCacheQueryable()
- .Join(GetOrgCache().AsCacheQueryable(), p => p.Value.OrganizationId, o => o.Value.Id,
- (p, o) => new
+ var qry = organizations.Join(
+ persons,
+ o => o.Value.Id,
+ p => p.Value.OrganizationId,
+ (org, person) => new
{
- PersonId = p.Key,
- PersonName = p.Value.Name.ToUpper(),
- OrgName = o.Value.Name
+ OrgName = org.Value.Name,
+ PersonAge = person.Value.Age
})
- .OrderBy(x => x.OrgName.ToLower())
- .ThenBy(x => x.PersonName)
- .ToArray();
+ .GroupBy(x => x.OrgName)
+ .Select(g => new {OrgId = g.Key, MaxAge = g.Max(x => x.PersonAge)})
+ .OrderBy(x => x.MaxAge);
- var expectedIds = Enumerable.Range(0, PersonCount)
- .OrderBy(x => (x % 2).ToString())
- .ThenBy(x => x.ToString())
- .ToArray();
+ var res = qry.ToArray();
- var actualIds = personsByOrg.Select(x => x.PersonId).ToArray();
+ Assert.AreEqual(2, res.Length);
- Assert.AreEqual(expectedIds, actualIds);
+ Assert.AreEqual("Org_0", res[0].OrgId);
+ Assert.AreEqual(898, res[0].MaxAge);
+
+ Assert.AreEqual("Org_1", res[1].OrgId);
+ Assert.AreEqual(899, res[1].MaxAge);
+ }
+
+ private class Projection
+ {
+ public int PersonAge { get; set; }
+
+ public string OrgName { get; set; }
}
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs
index 06d54b6..50394fa 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Log/CustomLoggerTest.cs
@@ -140,7 +140,7 @@ namespace Apache.Ignite.Core.Tests.Log
var errFromJava = TestLogger.Entries.Single(x => x.Exception != null);
Assert.IsNotNull(errFromJava.Exception.InnerException);
- Assert.AreEqual("Error in func.",
+ Assert.AreEqual("Error in func.",
((ArithmeticException) errFromJava.Exception.InnerException).Message);
}
}
@@ -399,7 +399,7 @@ namespace Apache.Ignite.Core.Tests.Log
/// <summary>
/// Checks the last message.
/// </summary>
- private static void CheckLastMessage(LogLevel level, string message, object[] args = null,
+ private static void CheckLastMessage(LogLevel level, string message, object[] args = null,
IFormatProvider formatProvider = null, string category = null, string nativeErr = null, Exception e = null)
{
var msg = TestLogger.Entries.Last();
@@ -440,7 +440,7 @@ namespace Apache.Ignite.Core.Tests.Log
public override string ToString()
{
return string.Format("Level: {0}, Message: {1}, Args: {2}, FormatProvider: {3}, Category: {4}, " +
- "NativeErrorInfo: {5}, Exception: {6}", Level, Message, Args, FormatProvider,
+ "NativeErrorInfo: {5}, Exception: {6}", Level, Message, Args, FormatProvider,
Category, NativeErrorInfo, Exception);
}
}
@@ -470,7 +470,7 @@ namespace Apache.Ignite.Core.Tests.Log
}
}
- public void Log(LogLevel level, string message, object[] args, IFormatProvider formatProvider,
+ public void Log(LogLevel level, string message, object[] args, IFormatProvider formatProvider,
string category, string nativeErrorInfo, Exception ex)
{
if (!IsEnabled(level))
@@ -538,6 +538,7 @@ namespace Apache.Ignite.Core.Tests.Log
/// </summary>
private struct CustomEnum
{
+ // ReSharper disable once UnusedMember.Local
public int Field { get; set; }
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
index 15710db..495f422 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
@@ -17,7 +17,6 @@
namespace Apache.Ignite.Linq.Impl
{
- using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -75,7 +74,7 @@ namespace Apache.Ignite.Linq.Impl
{
Debug.Assert(expression != null);
- return GetTableAlias(GetQuerySource(expression));
+ return GetTableAlias(ExpressionWalker.GetQuerySource(expression));
}
/// <summary>
@@ -83,7 +82,7 @@ namespace Apache.Ignite.Linq.Impl
/// </summary>
public string GetTableAlias(IFromClause fromClause)
{
- return GetTableAlias(GetQuerySource(fromClause.FromExpression) ?? fromClause);
+ return GetTableAlias(ExpressionWalker.GetQuerySource(fromClause.FromExpression) ?? fromClause);
}
/// <summary>
@@ -91,7 +90,7 @@ namespace Apache.Ignite.Linq.Impl
/// </summary>
public string GetTableAlias(JoinClause joinClause)
{
- return GetTableAlias(GetQuerySource(joinClause.InnerSequence) ?? joinClause);
+ return GetTableAlias(ExpressionWalker.GetQuerySource(joinClause.InnerSequence) ?? joinClause);
}
/// <summary>
@@ -159,41 +158,5 @@ namespace Apache.Ignite.Linq.Impl
return builder;
}
-
- /// <summary>
- /// Gets the query source.
- /// </summary>
- private static IQuerySource GetQuerySource(Expression expression)
- {
- var subQueryExp = expression as SubQueryExpression;
-
- if (subQueryExp != null)
- return GetQuerySource(subQueryExp.QueryModel.MainFromClause.FromExpression)
- ?? subQueryExp.QueryModel.MainFromClause;
-
- var srcRefExp = expression as QuerySourceReferenceExpression;
-
- if (srcRefExp != null)
- {
- var fromSource = srcRefExp.ReferencedQuerySource as IFromClause;
-
- if (fromSource != null)
- return GetQuerySource(fromSource.FromExpression) ?? fromSource;
-
- var joinSource = srcRefExp.ReferencedQuerySource as JoinClause;
-
- if (joinSource != null)
- return GetQuerySource(joinSource.InnerSequence) ?? joinSource;
-
- throw new NotSupportedException("Unexpected query source: " + srcRefExp.ReferencedQuerySource);
- }
-
- var memberExpr = expression as MemberExpression;
-
- if (memberExpr != null)
- return GetQuerySource(memberExpr.Expression);
-
- return null;
- }
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
index 91bde9a0..8cfb604 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
@@ -324,6 +324,10 @@ namespace Apache.Ignite.Linq.Impl
if (queryable != null)
{
+ // Find where the projection comes from.
+ expression = ExpressionWalker.GetProjectedMember(expression.Expression, expression.Member) ??
+ expression;
+
var fieldName = GetEscapedFieldName(expression, queryable);
ResultBuilder.AppendFormat("{0}.{1}", Aliases.GetTableAlias(expression), fieldName);
@@ -334,7 +338,6 @@ namespace Apache.Ignite.Linq.Impl
return expression;
}
-
/// <summary>
/// Gets the name of the field from a member expression, with quotes when necessary.
/// </summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
index 9a684d9..e49bd32 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
@@ -116,10 +116,69 @@ namespace Apache.Ignite.Linq.Impl
throw new NotSupportedException("Unexpected query source: " + expression);
return null;
+ }
+
+ /// <summary>
+ /// Gets the projected member.
+ /// Queries can have multiple projections, e.g. <c>qry.Select(person => new {Foo = person.Name})</c>.
+ /// This method finds the original member expression from the given projected expression, e.g finds
+ /// <c>Person.Name</c> from <c>Foo</c>.
+ /// </summary>
+ public static MemberExpression GetProjectedMember(Expression expression, MemberInfo memberHint)
+ {
+ Debug.Assert(memberHint != null);
+
+ var subQueryExp = expression as SubQueryExpression;
+ if (subQueryExp != null)
+ {
+ var selector = subQueryExp.QueryModel.SelectClause.Selector;
+ var newExpr = selector as NewExpression;
+
+ if (newExpr != null)
+ {
+ Debug.Assert(newExpr.Members.Count == newExpr.Arguments.Count);
+
+ for (var i = 0; i < newExpr.Members.Count; i++)
+ {
+ var member = newExpr.Members[i];
+
+ if (member == memberHint)
+ {
+ return newExpr.Arguments[i] as MemberExpression;
+ }
+ }
+ }
+
+ var initExpr = selector as MemberInitExpression;
+
+ if (initExpr != null)
+ {
+ foreach (var binding in initExpr.Bindings)
+ {
+ if (binding.Member == memberHint && binding.BindingType == MemberBindingType.Assignment)
+ {
+ return ((MemberAssignment)binding).Expression as MemberExpression;
+ }
+ }
+ }
+
+ return GetProjectedMember(subQueryExp.QueryModel.MainFromClause.FromExpression, memberHint);
+ }
+
+ var srcRefExp = expression as QuerySourceReferenceExpression;
+ if (srcRefExp != null)
+ {
+ var fromSource = srcRefExp.ReferencedQuerySource as IFromClause;
+
+ if (fromSource != null)
+ return GetProjectedMember(fromSource.FromExpression, memberHint);
+ }
+
+ return null;
}
/// <summary>
- /// Tries to find QuerySourceReferenceExpression
+ /// Gets the original QuerySourceReferenceExpression.
/// </summary>
public static QuerySourceReferenceExpression GetQuerySourceReference(Expression expression,
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global
@@ -152,6 +211,75 @@ namespace Apache.Ignite.Linq.Impl
}
/// <summary>
+ /// Gets the query source.
+ /// </summary>
+ public static IQuerySource GetQuerySource(Expression expression, MemberExpression memberHint = null)
+ {
+ if (memberHint != null)
+ {
+ var newExpr = expression as NewExpression;
+
+ if (newExpr != null)
+ {
+ for (var i = 0; i < newExpr.Members.Count; i++)
+ {
+ var member = newExpr.Members[i];
+
+ if (member == memberHint.Member)
+ {
+ return GetQuerySource(newExpr.Arguments[i]);
+ }
+ }
+ }
+ }
+
+ var subQueryExp = expression as SubQueryExpression;
+
+ if (subQueryExp != null)
+ {
+ var source = GetQuerySource(subQueryExp.QueryModel.SelectClause.Selector, memberHint);
+ if (source != null)
+ {
+ return source;
+ }
+
+ return subQueryExp.QueryModel.MainFromClause;
+ }
+
+ var srcRefExp = expression as QuerySourceReferenceExpression;
+
+ if (srcRefExp != null)
+ {
+ var fromSource = srcRefExp.ReferencedQuerySource as IFromClause;
+
+ if (fromSource != null)
+ {
+ var source = GetQuerySource(fromSource.FromExpression, memberHint);
+ if (source != null)
+ {
+ return source;
+ }
+
+ return fromSource;
+ }
+
+ var joinSource = srcRefExp.ReferencedQuerySource as JoinClause;
+
+ if (joinSource != null)
+ return GetQuerySource(joinSource.InnerSequence, memberHint) ?? joinSource;
+
+ throw new NotSupportedException("Unexpected query source: " + srcRefExp.ReferencedQuerySource);
+ }
+
+ var memberExpr = expression as MemberExpression;
+
+ if (memberExpr != null)
+ return GetQuerySource(memberExpr.Expression, memberExpr);
+
+ return null;
+ }
+
+ /// <summary>
/// Evaluates the expression.
/// </summary>
public static T EvaluateExpression<T>(Expression expr)