diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs index ad73adf956..29dbe35872 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs @@ -29,7 +29,7 @@ public static bool IsContainsQuery(this Expression expression) internal static object Invoke(this Expression expression) { return FastExpression.Lambda( - WellKnownMembers.FuncOfTResultType.CachedMakeGenericType(expression.Type), expression).Compile().DynamicInvoke(); + WellKnownTypes.FuncOfTResultType.CachedMakeGenericType(expression.Type), expression).Compile().DynamicInvoke(); } internal static Expression Visit(this Expression exp, Func visitor) where T : Expression diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs index b295a17517..b3558c5687 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using Xtensive.Core; +using Xtensive.Linq; using Xtensive.Orm.Linq; using Xtensive.Orm.Model; using Xtensive.Reflection; @@ -29,43 +30,52 @@ protected override int ExecuteInternal() { var e = query.Expression.Visit((MethodCallExpression ex) => { - var methodInfo = ex.Method; - //rewrite localCollection.Contains(entity.SomeField) -> entity.SomeField.In(localCollection) - if (methodInfo.DeclaringType == WellKnownMembers.EnumerableType && - string.Equals(methodInfo.Name, "Contains", StringComparison.Ordinal) && - ex.Arguments.Count == 2) { - var localCollection = ex.Arguments[0];//IEnumerable + var methodInfo = ex.Method; + // both of names are 'Contains' + if (string.Equals(methodInfo.Name, WellKnownMembers.SpanContainsExtensionName, StringComparison.Ordinal) + /*|| string.Equals(methodInfo.Name, WellKnownMembers.SpanContainsExtensionName, StringComparison.Ordinal)*/) + { + Expression localCollection = null; + + if (methodInfo.DeclaringType == WellKnownTypes.MemoryExtensionsType) { + localCollection = ex.Arguments[0].StripImplicitCast();// array with implicit cast to span + } + if (methodInfo.DeclaringType == WellKnownTypes.EnumerableType) + localCollection = ex.Arguments[0]; // IEnumerable + + if (localCollection is not null) { var valueToCheck = ex.Arguments[1]; var genericInMethod = WellKnownMembers.InMethod.CachedMakeGenericMethod(valueToCheck.Type); ex = Expression.Call(genericInMethod, valueToCheck, Expression.Constant(IncludeAlgorithm.ComplexCondition), localCollection); methodInfo = ex.Method; } + } - if (methodInfo.DeclaringType == WellKnownMembers.QueryableExtensionsType && - string.Equals(methodInfo.Name, WellKnownMembers.InMethodName, StringComparison.Ordinal) && - ex.Arguments.Count > 1) { - if (ex.Arguments[1].Type == WellKnownMembers.IncludeAlgorithmType) { - var algorithm = (IncludeAlgorithm) ex.Arguments[1].Invoke(); - if (algorithm == IncludeAlgorithm.TemporaryTable) { - throw new NotSupportedException("IncludeAlgorithm.TemporaryTable is not supported"); - } - - if (algorithm == IncludeAlgorithm.Auto) { - var arguments = ex.Arguments.ToList(); - arguments[1] = Expression.Constant(IncludeAlgorithm.ComplexCondition); - ex = Expression.Call(methodInfo, arguments); - } + if (methodInfo.DeclaringType == WellKnownTypes.QueryableExtensionsType && + string.Equals(methodInfo.Name, WellKnownMembers.InMethodName, StringComparison.Ordinal) && + ex.Arguments.Count > 1) { + if (ex.Arguments[1].Type == WellKnownTypes.IncludeAlgorithmType) { + var algorithm = (IncludeAlgorithm) ex.Arguments[1].Invoke(); + if (algorithm == IncludeAlgorithm.TemporaryTable) { + throw new NotSupportedException("IncludeAlgorithm.TemporaryTable is not supported"); } - else { + + if (algorithm == IncludeAlgorithm.Auto) { var arguments = ex.Arguments.ToList(); - arguments.Insert(1, Expression.Constant(IncludeAlgorithm.ComplexCondition)); - ex = Expression.Call(WellKnownMembers.InMethod.MakeGenericMethod(methodInfo.GetGenericArguments()), - arguments.ToArray()); + arguments[1] = Expression.Constant(IncludeAlgorithm.ComplexCondition); + ex = Expression.Call(methodInfo, arguments); } } + else { + var arguments = ex.Arguments.ToList(); + arguments.Insert(1, Expression.Constant(IncludeAlgorithm.ComplexCondition)); + ex = Expression.Call(WellKnownMembers.InMethod.MakeGenericMethod(methodInfo.GetGenericArguments()), + arguments.ToArray()); + } + } - return ex; - }); + return ex; + }); query = QueryProvider.CreateQuery(e); return 0; } diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs index 6c7ebfb7e5..d852c85fbc 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs @@ -40,7 +40,7 @@ public void AddValues() Descriptor = descriptor, Lambda = FastExpression.Lambda( - WellKnownMembers.FuncOfTArgTResultType.CachedMakeGenericType(typeof(T), descriptor.Expression.Type), + WellKnownTypes.FuncOfTArgTResultType.CachedMakeGenericType(typeof(T), descriptor.Expression.Type), descriptor.Expression, descriptor.Parameter), Statement = Statement, @@ -96,7 +96,7 @@ private void PreprocessStructures() _ = Descriptors.Remove(setDescriptor); var exp = setDescriptor.Expression; //var call = ex as MethodCallExpression; - if (exp is MethodCallExpression call && call.Method.DeclaringType == WellKnownMembers.QueryableType + if (exp is MethodCallExpression call && call.Method.DeclaringType == WellKnownTypes.QueryableType && call.Method.Name is nameof(Queryable.First) or nameof(Queryable.FirstOrDefault) or nameof(Queryable.Single) or nameof(Queryable.SingleOrDefault)) { throw new NotSupportedException("Subqueries with structures are not supported"); @@ -126,7 +126,7 @@ private void AddComputedStaticExpression(AddValueContext addContext) var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Column.Name); var all = Expression.Call(Expression.Constant(parent.Session.Query), nameof(QueryEndpoint.All), new[] { typeof(T) }); var selectExpression = Expression.Call( - WellKnownMembers.QueryableType, + WellKnownTypes.QueryableType, nameof(Queryable.OrderBy), addContext.Lambda.Type.GetGenericArguments(), all, @@ -156,7 +156,7 @@ private void AddComputedExpression(AddValueContext addContext) var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Column.Name); var all = Expression.Call(Expression.Constant(parent.Session.Query), nameof(QueryEndpoint.All), new[] {typeof (T)}); var selectExpression = Expression.Call( - WellKnownMembers.QueryableType, + WellKnownTypes.QueryableType, nameof(Queryable.OrderBy), addContext.Lambda.Type.GetGenericArguments(), all, @@ -235,29 +235,29 @@ private void AddEntityValue(AddValueContext addContext) } return; } - if (methodCall.Method.DeclaringType == WellKnownMembers.QueryableType + if (methodCall.Method.DeclaringType == WellKnownTypes.QueryableType && (methodCall.Method.Name is nameof(Queryable.Single) or nameof(Queryable.SingleOrDefault) or nameof(Queryable.First) or nameof(Queryable.FirstOrDefault))) { var exp = methodCall.Arguments[0]; var fieldValueType = parent.GetTypeInfo(addContext.Field.ValueType); if (methodCall.Arguments.Count == 2) { - exp = Expression.Call(WellKnownMembers.QueryableType, + exp = Expression.Call(WellKnownTypes.QueryableType, nameof(Queryable.Where), new[] { fieldValueType.UnderlyingType }, exp, methodCall.Arguments[1]); } - exp = Expression.Call(WellKnownMembers.QueryableType, nameof(Queryable.Take), new[] {fieldValueType.UnderlyingType}, exp, Expression.Constant(1)); + exp = Expression.Call(WellKnownTypes.QueryableType, nameof(Queryable.Take), new[] {fieldValueType.UnderlyingType}, exp, Expression.Constant(1)); i = -1; foreach (var field in fieldValueType.Key.Fields) { i++; var p = Expression.Parameter(fieldValueType.UnderlyingType); var lambda = FastExpression.Lambda( - WellKnownMembers.FuncOfTArgTResultType.CachedMakeGenericType(fieldValueType.UnderlyingType, field.ValueType), + WellKnownTypes.FuncOfTArgTResultType.CachedMakeGenericType(fieldValueType.UnderlyingType, field.ValueType), Expression.MakeMemberAccess(p, field.UnderlyingProperty), p); var q = ((IQueryProvider) parent.QueryProvider) .CreateQuery(Expression.Call( - WellKnownMembers.QueryableType, + WellKnownTypes.QueryableType, nameof(Queryable.Select), new[] { fieldValueType.UnderlyingType, field.ValueType }, exp, @@ -266,7 +266,6 @@ or nameof(Queryable.First) or nameof(Queryable.FirstOrDefault))) { parent.Bindings.AddRange(request.ParameterBindings); var column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Columns[i].Name); addContext.Values.Add(column, SqlDml.SubQuery(request.Query)); - //addContext.Statement.AddValue(c, SqlDml.SubQuery(request.Query)); } return; } diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownMembers.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownMembers.cs index 982f2c40a6..e4091b3060 100644 --- a/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownMembers.cs +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownMembers.cs @@ -11,15 +11,9 @@ namespace Xtensive.Orm.BulkOperations { internal static class WellKnownMembers { - public static readonly Type EnumerableType = typeof(Enumerable); - public static readonly Type FuncOfTResultType = typeof(Func<>); - public static readonly Type FuncOfTArgTResultType = typeof(Func<,>); - - public static readonly Type IncludeAlgorithmType = typeof(IncludeAlgorithm); - public static readonly Type QueryableType = typeof(Queryable); - public static readonly Type QueryableExtensionsType = typeof(QueryableExtensionsEx); - public const string InMethodName = nameof(QueryableExtensionsEx.In); + public const string SpanContainsExtensionName = nameof(MemoryExtensions.Contains); + public const string EnumerableContainsName = nameof(Enumerable.Contains); public static readonly MethodInfo TranslateQueryMethod = typeof(QueryBuilder).GetMethod(nameof(QueryBuilder.TranslateQuery)); @@ -28,7 +22,7 @@ internal static class WellKnownMembers private static MethodInfo GetInMethod() { - foreach (var method in QueryableExtensionsType.GetMethods().Where(a => a.Name == InMethodName)) { + foreach (var method in WellKnownTypes.QueryableExtensionsType.GetMethods().Where(a => a.Name == InMethodName)) { var parameters = method.GetParameters(); if (parameters.Length == 3 && parameters[2].ParameterType.Name == "IEnumerable`1") { return method; diff --git a/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownTypes.cs b/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownTypes.cs new file mode 100644 index 0000000000..e7d91d918a --- /dev/null +++ b/Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownTypes.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2020-2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Linq; + +namespace Xtensive.Orm.BulkOperations +{ + internal static class WellKnownTypes + { + public static readonly Type EnumerableType = typeof(Enumerable); + public static readonly Type FuncOfTResultType = typeof(Func<>); + public static readonly Type FuncOfTArgTResultType = typeof(Func<,>); + + public static readonly Type IncludeAlgorithmType = typeof(IncludeAlgorithm); + public static readonly Type QueryableType = typeof(Queryable); + public static readonly Type QueryableExtensionsType = typeof(QueryableExtensionsEx); + public static readonly Type MemoryExtensionsType = typeof(MemoryExtensions); + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs b/Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs index 370e34bf4b..08f11e4dec 100644 --- a/Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs +++ b/Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs @@ -83,7 +83,7 @@ public static Expression LiftToNullable(this Expression expression) => /// /// Possible candidate for transformation. /// New instance of expression, if transformation was required, otherwise, the same expression. - public static MethodCallExpression TryTransformToOldFashionContains(this MethodCallExpression mc) + public static MethodCallExpression TryTransformToEnumerableContains(this MethodCallExpression mc) { if (mc.Method.DeclaringType == MemoryExtensionsType) { var genericMethod = mc.Method.GetGenericMethodDefinition(); diff --git a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs index ecc79ac6f0..c7af6d99e1 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs @@ -36,6 +36,7 @@ internal sealed partial class Translator { private static readonly Type OrmCollectionExtensionsType = typeof(CollectionExtensionsEx); private static readonly Type OrmQueryableExtensionsType = typeof(QueryableExtensionsEx); + private static readonly Type SystemMemoryExtensionsType = typeof(System.MemoryExtensions); private static readonly ParameterExpression ParameterContextParam = Expression.Parameter(WellKnownOrmTypes.ParameterContext, "context"); private static readonly ConstantExpression NullKeyExpression = Expression.Constant(null, WellKnownOrmTypes.Key), @@ -503,7 +504,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc) #pragma warning restore 612,618 // Visit Queryable extensions. - if (methodDeclaringType == typeof(QueryableExtensionsEx)) { + if (methodDeclaringType == OrmQueryableExtensionsType) { return methodName switch { #if !NET10_0_OR_GREATER Reflection.WellKnown.QueryableExtensions.LeftJoin => VisitLeftJoin(mc), @@ -521,7 +522,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc) }; } // Visit Collection extensions - if (methodDeclaringType == typeof(CollectionExtensionsEx)) { + if (methodDeclaringType == OrmCollectionExtensionsType) { switch (methodName) { case Reflection.WellKnown.CollectionExtensions.ContainsAny: return VisitContainsAny(mc.Arguments[0], mc.Arguments[1], context.IsRoot(mc), method.GetGenericArguments()[0]); @@ -531,9 +532,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc) return VisitContainsNone(mc.Arguments[0], mc.Arguments[1], context.IsRoot(mc), method.GetGenericArguments()[0]); } } - if (methodDeclaringType == typeof(System.MemoryExtensions)) { - var parameters = method.GetParameters(); - + if (methodDeclaringType == SystemMemoryExtensionsType) { if (methodName.Equals(nameof(System.MemoryExtensions.Contains), StringComparison.Ordinal)){ // There might be 2 or 3 arguments. // In case of three, last one is IEqualityComparer which will probably have default value diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs index a3fb4f667a..3d814929d5 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs @@ -409,12 +409,14 @@ protected override SqlExpression VisitMemberAccess(MemberExpression m) protected override SqlExpression VisitMethodCall(MethodCallExpression mc) { - if (mc.AsTupleAccess(activeParameters) != null) + if (mc.AsTupleAccess(activeParameters) is not null) return VisitTupleAccess(mc); - if (mc.Method.Name.Equals(nameof(Enumerable.Contains), StringComparison.Ordinal)) { - // there might be "innovative" implicit cast to ReadOnlySpan inside, which is not supported by expression tree but yet existing - mc = mc.TryTransformToOldFashionContains(); + if (mc.Method.Name.Equals(nameof(MemoryExtensions.Contains), StringComparison.Ordinal)) { + // there might be "innovative" implicit cast to ReadOnlySpan inside, + // which is not supported by expression tree but yet exist in Linq expressions + // created by dotnet compiler + mc = mc.TryTransformToEnumerableContains(); } var arguments = mc.Arguments.SelectToArray(a => Visit(a)); var mi = mc.Method;