Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(this Expression exp, Func<T, Expression> visitor) where T : Expression
Expand Down
62 changes: 36 additions & 26 deletions Extensions/Xtensive.Orm.BulkOperations/Internals/QueryOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<T>
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<T>

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<T>(e);
return 0;
}
Expand Down
19 changes: 9 additions & 10 deletions Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions Extensions/Xtensive.Orm.BulkOperations/Internals/WellKnownTypes.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 1 addition & 1 deletion Orm/Xtensive.Orm/Linq/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static Expression LiftToNullable(this Expression expression) =>
/// </summary>
/// <param name="mc">Possible candidate for transformation.</param>
/// <returns>New instance of expression, if transformation was required, otherwise, the same expression.</returns>
public static MethodCallExpression TryTransformToOldFashionContains(this MethodCallExpression mc)
public static MethodCallExpression TryTransformToEnumerableContains(this MethodCallExpression mc)
{
if (mc.Method.DeclaringType == MemoryExtensionsType) {
var genericMethod = mc.Method.GetGenericMethodDefinition();
Expand Down
9 changes: 4 additions & 5 deletions Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand All @@ -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]);
Expand All @@ -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<T> which will probably have default value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading