Skip to content

Commit e668b45

Browse files
committed
BulkOperations: handled memory extension Contains usage in .NET 10
also - split WellKnownMembers into members and types class containers - renamed one of Expresson extensions - applied cached types which were already there for method call expression visiting
1 parent 34e1f97 commit e668b45

8 files changed

Lines changed: 81 additions & 56 deletions

File tree

Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static bool IsContainsQuery(this Expression expression)
2929
internal static object Invoke(this Expression expression)
3030
{
3131
return FastExpression.Lambda(
32-
WellKnownMembers.FuncOfTResultType.CachedMakeGenericType(expression.Type), expression).Compile().DynamicInvoke();
32+
WellKnownTypes.FuncOfTResultType.CachedMakeGenericType(expression.Type), expression).Compile().DynamicInvoke();
3333
}
3434

3535
internal static Expression Visit<T>(this Expression exp, Func<T, Expression> visitor) where T : Expression

Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Linq.Expressions;
99
using Xtensive.Core;
10+
using Xtensive.Linq;
1011
using Xtensive.Orm.Linq;
1112
using Xtensive.Orm.Model;
1213
using Xtensive.Reflection;
@@ -29,43 +30,52 @@ protected override int ExecuteInternal()
2930
{
3031
var e = query.Expression.Visit((MethodCallExpression ex) => {
3132

32-
var methodInfo = ex.Method;
33-
//rewrite localCollection.Contains(entity.SomeField) -> entity.SomeField.In(localCollection)
34-
if (methodInfo.DeclaringType == WellKnownMembers.EnumerableType &&
35-
string.Equals(methodInfo.Name, "Contains", StringComparison.Ordinal) &&
36-
ex.Arguments.Count == 2) {
37-
var localCollection = ex.Arguments[0];//IEnumerable<T>
33+
var methodInfo = ex.Method;
34+
// both of names are 'Contains'
35+
if (string.Equals(methodInfo.Name, WellKnownMembers.SpanContainsExtensionName, StringComparison.Ordinal)
36+
/*|| string.Equals(methodInfo.Name, WellKnownMembers.SpanContainsExtensionName, StringComparison.Ordinal)*/)
37+
{
38+
Expression localCollection = null;
39+
40+
if (methodInfo.DeclaringType == WellKnownTypes.MemoryExtensionsType) {
41+
localCollection = ex.Arguments[0].StripImplicitCast();// array with implicit cast to span
42+
}
43+
if (methodInfo.DeclaringType == WellKnownTypes.EnumerableType)
44+
localCollection = ex.Arguments[0]; // IEnumerable<T>
45+
46+
if (localCollection is not null) {
3847
var valueToCheck = ex.Arguments[1];
3948
var genericInMethod = WellKnownMembers.InMethod.CachedMakeGenericMethod(valueToCheck.Type);
4049
ex = Expression.Call(genericInMethod, valueToCheck, Expression.Constant(IncludeAlgorithm.ComplexCondition), localCollection);
4150
methodInfo = ex.Method;
4251
}
52+
}
4353

44-
if (methodInfo.DeclaringType == WellKnownMembers.QueryableExtensionsType &&
45-
string.Equals(methodInfo.Name, WellKnownMembers.InMethodName, StringComparison.Ordinal) &&
46-
ex.Arguments.Count > 1) {
47-
if (ex.Arguments[1].Type == WellKnownMembers.IncludeAlgorithmType) {
48-
var algorithm = (IncludeAlgorithm) ex.Arguments[1].Invoke();
49-
if (algorithm == IncludeAlgorithm.TemporaryTable) {
50-
throw new NotSupportedException("IncludeAlgorithm.TemporaryTable is not supported");
51-
}
52-
53-
if (algorithm == IncludeAlgorithm.Auto) {
54-
var arguments = ex.Arguments.ToList();
55-
arguments[1] = Expression.Constant(IncludeAlgorithm.ComplexCondition);
56-
ex = Expression.Call(methodInfo, arguments);
57-
}
54+
if (methodInfo.DeclaringType == WellKnownTypes.QueryableExtensionsType &&
55+
string.Equals(methodInfo.Name, WellKnownMembers.InMethodName, StringComparison.Ordinal) &&
56+
ex.Arguments.Count > 1) {
57+
if (ex.Arguments[1].Type == WellKnownTypes.IncludeAlgorithmType) {
58+
var algorithm = (IncludeAlgorithm) ex.Arguments[1].Invoke();
59+
if (algorithm == IncludeAlgorithm.TemporaryTable) {
60+
throw new NotSupportedException("IncludeAlgorithm.TemporaryTable is not supported");
5861
}
59-
else {
62+
63+
if (algorithm == IncludeAlgorithm.Auto) {
6064
var arguments = ex.Arguments.ToList();
61-
arguments.Insert(1, Expression.Constant(IncludeAlgorithm.ComplexCondition));
62-
ex = Expression.Call(WellKnownMembers.InMethod.MakeGenericMethod(methodInfo.GetGenericArguments()),
63-
arguments.ToArray());
65+
arguments[1] = Expression.Constant(IncludeAlgorithm.ComplexCondition);
66+
ex = Expression.Call(methodInfo, arguments);
6467
}
6568
}
69+
else {
70+
var arguments = ex.Arguments.ToList();
71+
arguments.Insert(1, Expression.Constant(IncludeAlgorithm.ComplexCondition));
72+
ex = Expression.Call(WellKnownMembers.InMethod.MakeGenericMethod(methodInfo.GetGenericArguments()),
73+
arguments.ToArray());
74+
}
75+
}
6676

67-
return ex;
68-
});
77+
return ex;
78+
});
6979
query = QueryProvider.CreateQuery<T>(e);
7080
return 0;
7181
}

Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void AddValues()
4040
Descriptor = descriptor,
4141
Lambda =
4242
FastExpression.Lambda(
43-
WellKnownMembers.FuncOfTArgTResultType.CachedMakeGenericType(typeof(T), descriptor.Expression.Type),
43+
WellKnownTypes.FuncOfTArgTResultType.CachedMakeGenericType(typeof(T), descriptor.Expression.Type),
4444
descriptor.Expression,
4545
descriptor.Parameter),
4646
Statement = Statement,
@@ -96,7 +96,7 @@ private void PreprocessStructures()
9696
_ = Descriptors.Remove(setDescriptor);
9797
var exp = setDescriptor.Expression;
9898
//var call = ex as MethodCallExpression;
99-
if (exp is MethodCallExpression call && call.Method.DeclaringType == WellKnownMembers.QueryableType
99+
if (exp is MethodCallExpression call && call.Method.DeclaringType == WellKnownTypes.QueryableType
100100
&& call.Method.Name is nameof(Queryable.First) or nameof(Queryable.FirstOrDefault)
101101
or nameof(Queryable.Single) or nameof(Queryable.SingleOrDefault)) {
102102
throw new NotSupportedException("Subqueries with structures are not supported");
@@ -126,7 +126,7 @@ private void AddComputedStaticExpression(AddValueContext addContext)
126126
var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Column.Name);
127127
var all = Expression.Call(Expression.Constant(parent.Session.Query), nameof(QueryEndpoint.All), new[] { typeof(T) });
128128
var selectExpression = Expression.Call(
129-
WellKnownMembers.QueryableType,
129+
WellKnownTypes.QueryableType,
130130
nameof(Queryable.OrderBy),
131131
addContext.Lambda.Type.GetGenericArguments(),
132132
all,
@@ -156,7 +156,7 @@ private void AddComputedExpression(AddValueContext addContext)
156156
var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Column.Name);
157157
var all = Expression.Call(Expression.Constant(parent.Session.Query), nameof(QueryEndpoint.All), new[] {typeof (T)});
158158
var selectExpression = Expression.Call(
159-
WellKnownMembers.QueryableType,
159+
WellKnownTypes.QueryableType,
160160
nameof(Queryable.OrderBy),
161161
addContext.Lambda.Type.GetGenericArguments(),
162162
all,
@@ -235,29 +235,29 @@ private void AddEntityValue(AddValueContext addContext)
235235
}
236236
return;
237237
}
238-
if (methodCall.Method.DeclaringType == WellKnownMembers.QueryableType
238+
if (methodCall.Method.DeclaringType == WellKnownTypes.QueryableType
239239
&& (methodCall.Method.Name is nameof(Queryable.Single) or nameof(Queryable.SingleOrDefault)
240240
or nameof(Queryable.First) or nameof(Queryable.FirstOrDefault))) {
241241

242242
var exp = methodCall.Arguments[0];
243243
var fieldValueType = parent.GetTypeInfo(addContext.Field.ValueType);
244244
if (methodCall.Arguments.Count == 2) {
245-
exp = Expression.Call(WellKnownMembers.QueryableType,
245+
exp = Expression.Call(WellKnownTypes.QueryableType,
246246
nameof(Queryable.Where), new[] { fieldValueType.UnderlyingType }, exp, methodCall.Arguments[1]);
247247
}
248-
exp = Expression.Call(WellKnownMembers.QueryableType, nameof(Queryable.Take), new[] {fieldValueType.UnderlyingType}, exp, Expression.Constant(1));
248+
exp = Expression.Call(WellKnownTypes.QueryableType, nameof(Queryable.Take), new[] {fieldValueType.UnderlyingType}, exp, Expression.Constant(1));
249249
i = -1;
250250
foreach (var field in fieldValueType.Key.Fields) {
251251
i++;
252252
var p = Expression.Parameter(fieldValueType.UnderlyingType);
253253
var lambda =
254254
FastExpression.Lambda(
255-
WellKnownMembers.FuncOfTArgTResultType.CachedMakeGenericType(fieldValueType.UnderlyingType, field.ValueType),
255+
WellKnownTypes.FuncOfTArgTResultType.CachedMakeGenericType(fieldValueType.UnderlyingType, field.ValueType),
256256
Expression.MakeMemberAccess(p, field.UnderlyingProperty),
257257
p);
258258
var q = ((IQueryProvider) parent.QueryProvider)
259259
.CreateQuery(Expression.Call(
260-
WellKnownMembers.QueryableType,
260+
WellKnownTypes.QueryableType,
261261
nameof(Queryable.Select),
262262
new[] { fieldValueType.UnderlyingType, field.ValueType },
263263
exp,
@@ -266,7 +266,6 @@ or nameof(Queryable.First) or nameof(Queryable.FirstOrDefault))) {
266266
parent.Bindings.AddRange(request.ParameterBindings);
267267
var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Columns[i].Name);
268268
addContext.Values.Add(column, SqlDml.SubQuery(request.Query));
269-
//addContext.Statement.AddValue(c, SqlDml.SubQuery(request.Query));
270269
}
271270
return;
272271
}

Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownMembers.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,9 @@ namespace Xtensive.Orm.BulkOperations
1111
{
1212
internal static class WellKnownMembers
1313
{
14-
public static readonly Type EnumerableType = typeof(Enumerable);
15-
public static readonly Type FuncOfTResultType = typeof(Func<>);
16-
public static readonly Type FuncOfTArgTResultType = typeof(Func<,>);
17-
18-
public static readonly Type IncludeAlgorithmType = typeof(IncludeAlgorithm);
19-
public static readonly Type QueryableType = typeof(Queryable);
20-
public static readonly Type QueryableExtensionsType = typeof(QueryableExtensionsEx);
21-
2214
public const string InMethodName = nameof(QueryableExtensionsEx.In);
15+
public const string SpanContainsExtensionName = nameof(MemoryExtensions.Contains);
16+
public const string EnumerableContainsName = nameof(Enumerable.Contains);
2317

2418
public static readonly MethodInfo TranslateQueryMethod =
2519
typeof(QueryBuilder).GetMethod(nameof(QueryBuilder.TranslateQuery));
@@ -28,7 +22,7 @@ internal static class WellKnownMembers
2822

2923
private static MethodInfo GetInMethod()
3024
{
31-
foreach (var method in QueryableExtensionsType.GetMethods().Where(a => a.Name == InMethodName)) {
25+
foreach (var method in WellKnownTypes.QueryableExtensionsType.GetMethods().Where(a => a.Name == InMethodName)) {
3226
var parameters = method.GetParameters();
3327
if (parameters.Length == 3 && parameters[2].ParameterType.Name == "IEnumerable`1") {
3428
return method;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (C) 2020-2023 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Linq;
7+
8+
namespace Xtensive.Orm.BulkOperations
9+
{
10+
internal static class WellKnownTypes
11+
{
12+
public static readonly Type EnumerableType = typeof(Enumerable);
13+
public static readonly Type FuncOfTResultType = typeof(Func<>);
14+
public static readonly Type FuncOfTArgTResultType = typeof(Func<,>);
15+
16+
public static readonly Type IncludeAlgorithmType = typeof(IncludeAlgorithm);
17+
public static readonly Type QueryableType = typeof(Queryable);
18+
public static readonly Type QueryableExtensionsType = typeof(QueryableExtensionsEx);
19+
public static readonly Type MemoryExtensionsType = typeof(MemoryExtensions);
20+
}
21+
}

Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static Expression LiftToNullable(this Expression expression) =>
8383
/// </summary>
8484
/// <param name="mc">Possible candidate for transformation.</param>
8585
/// <returns>New instance of expression, if transformation was required, otherwise, the same expression.</returns>
86-
public static MethodCallExpression TryTransformToOldFashionContains(this MethodCallExpression mc)
86+
public static MethodCallExpression TryTransformToEnumerableContains(this MethodCallExpression mc)
8787
{
8888
if (mc.Method.DeclaringType == MemoryExtensionsType) {
8989
var genericMethod = mc.Method.GetGenericMethodDefinition();

Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal sealed partial class Translator
3636
{
3737
private static readonly Type OrmCollectionExtensionsType = typeof(CollectionExtensionsEx);
3838
private static readonly Type OrmQueryableExtensionsType = typeof(QueryableExtensionsEx);
39+
private static readonly Type SystemMemoryExtensionsType = typeof(System.MemoryExtensions);
3940
private static readonly ParameterExpression ParameterContextParam = Expression.Parameter(WellKnownOrmTypes.ParameterContext, "context");
4041
private static readonly ConstantExpression
4142
NullKeyExpression = Expression.Constant(null, WellKnownOrmTypes.Key),
@@ -503,7 +504,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc)
503504
#pragma warning restore 612,618
504505

505506
// Visit Queryable extensions.
506-
if (methodDeclaringType == typeof(QueryableExtensionsEx)) {
507+
if (methodDeclaringType == OrmQueryableExtensionsType) {
507508
return methodName switch {
508509
#if !NET10_0_OR_GREATER
509510
Reflection.WellKnown.QueryableExtensions.LeftJoin => VisitLeftJoin(mc),
@@ -521,7 +522,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc)
521522
};
522523
}
523524
// Visit Collection extensions
524-
if (methodDeclaringType == typeof(CollectionExtensionsEx)) {
525+
if (methodDeclaringType == OrmCollectionExtensionsType) {
525526
switch (methodName) {
526527
case Reflection.WellKnown.CollectionExtensions.ContainsAny:
527528
return VisitContainsAny(mc.Arguments[0], mc.Arguments[1], context.IsRoot(mc), method.GetGenericArguments()[0]);
@@ -531,9 +532,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc)
531532
return VisitContainsNone(mc.Arguments[0], mc.Arguments[1], context.IsRoot(mc), method.GetGenericArguments()[0]);
532533
}
533534
}
534-
if (methodDeclaringType == typeof(System.MemoryExtensions)) {
535-
var parameters = method.GetParameters();
536-
535+
if (methodDeclaringType == SystemMemoryExtensionsType) {
537536
if (methodName.Equals(nameof(System.MemoryExtensions.Contains), StringComparison.Ordinal)){
538537
// There might be 2 or 3 arguments.
539538
// In case of three, last one is IEqualityComparer<T> which will probably have default value

Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,14 @@ protected override SqlExpression VisitMemberAccess(MemberExpression m)
409409

410410
protected override SqlExpression VisitMethodCall(MethodCallExpression mc)
411411
{
412-
if (mc.AsTupleAccess(activeParameters) != null)
412+
if (mc.AsTupleAccess(activeParameters) is not null)
413413
return VisitTupleAccess(mc);
414414

415-
if (mc.Method.Name.Equals(nameof(Enumerable.Contains), StringComparison.Ordinal)) {
416-
// there might be "innovative" implicit cast to ReadOnlySpan inside, which is not supported by expression tree but yet existing
417-
mc = mc.TryTransformToOldFashionContains();
415+
if (mc.Method.Name.Equals(nameof(MemoryExtensions.Contains), StringComparison.Ordinal)) {
416+
// there might be "innovative" implicit cast to ReadOnlySpan inside,
417+
// which is not supported by expression tree but yet exist in Linq expressions
418+
// created by dotnet compiler
419+
mc = mc.TryTransformToEnumerableContains();
418420
}
419421
var arguments = mc.Arguments.SelectToArray(a => Visit(a));
420422
var mi = mc.Method;

0 commit comments

Comments
 (0)