IBuildContext ApplyQueryFilters(ExpressionBuilder builder, BuildInfo buildInfo, MemberInfo?memberInfo, TableContext tableContext) { var entityType = tableContext.ObjectType; if (builder.IsFilterDisabled(entityType)) { return(tableContext); } var ed = builder.MappingSchema.GetEntityDescriptor(entityType); var filterFunc = ed.QueryFilterFunc; if (filterFunc == null) { return(tableContext); } if (memberInfo == null) { memberInfo = Methods.LinqToDB.GetTable.MakeGenericMethod(entityType); } var fakeQuery = ExpressionQueryImpl.CreateQuery(entityType, builder.DataContext, null); // Here we tell for Equality Comparer to compare optimized expressions // var closureMappingSchema = builder.MappingSchema; builder.AddQueryableMemberAccessors(memberInfo, mi => { var filtered = (IQueryable)filterFunc.DynamicInvoke(fakeQuery, builder.DataContext); // here we use light version of optimization, only for comparing trees var optimizationContext = new ExpressionTreeOptimizationContext(closureMappingSchema); var optimizedExpr = optimizationContext.ExposeExpression(filtered.Expression); optimizedExpr = optimizationContext.ExpandQueryableMethods(optimizedExpr); optimizedExpr = optimizedExpr.OptimizeExpression() !; return(optimizedExpr); }); var filtered = (IQueryable)filterFunc.DynamicInvoke(fakeQuery, builder.DataContext); var optimized = filtered.Expression; optimized = builder.ConvertExpressionTree(optimized); optimized = builder.ConvertExpression(optimized); optimized = optimized.OptimizeExpression() !; var refExpression = new ContextRefExpression(typeof(IQueryable <>).MakeGenericType(entityType), tableContext); var replaced = optimized.Replace(fakeQuery.Expression, refExpression); if (replaced == optimized) { throw new LinqException("Could not correct query result for processing."); } var context = builder.BuildSequence(new BuildInfo(buildInfo, replaced)); return(context); }
public ParametersContext(Expression parametersExpression, ExpressionTreeOptimizationContext optimizationContext, IDataContext dataContext) { ParametersExpression = parametersExpression; OptimizationContext = optimizationContext; DataContext = dataContext; _expressionAccessors = parametersExpression.GetExpressionAccessors(ExpressionBuilder.ExpressionParam); }
// Returns // (ParentType p) => dc.GetTable<ObjectType>().Where(...) // (ParentType p) => dc.GetTable<ObjectType>().Where(...).DefaultIfEmpty public static LambdaExpression CreateAssociationQueryLambda(ExpressionBuilder builder, AccessorMember onMember, AssociationDescriptor association, Type parentOriginalType, Type parentType, Type objectType, bool inline, bool enforceDefault, List <LoadWithInfo[]>?loadWith, out bool isLeft) { var dataContextConstant = Expression.Constant(builder.DataContext, builder.DataContext.GetType()); // We are trying to keep fast cache hit behaviour, so cache check should be added only if needed // bool shouldAddCacheCheck = false; bool cacheCheckAdded = false; LambdaExpression?definedQueryMethod = null; if (association.HasQueryMethod()) { // here we tell for Expression Comparer to compare optimized Association expressions // definedQueryMethod = (LambdaExpression)builder.AddQueryableMemberAccessors(onMember, builder.DataContext, (mi, dc) => { var queryLambda = association.GetQueryMethod(parentType, objectType) ?? throw new InvalidOperationException(); var optimizationContext = new ExpressionTreeOptimizationContext(dc); var optimizedExpr = optimizationContext.ExposeExpression(queryLambda); optimizedExpr = optimizationContext.ExpandQueryableMethods(optimizedExpr); return(optimizedExpr); }); cacheCheckAdded = true; var parameterMatch = new Dictionary <ParameterExpression, Expression>(); if (onMember.Arguments == null) { if (definedQueryMethod.Parameters.Count > 1 && typeof(IDataContext).IsSameOrParentOf(definedQueryMethod.Parameters[1].Type)) { parameterMatch.Add(definedQueryMethod.Parameters[1], dataContextConstant); } } else { var definedCount = definedQueryMethod.Parameters.Count; var argumentsCount = onMember.Arguments.Count; var diff = definedCount - argumentsCount; for (int i = definedCount - 1; i >= diff; i--) { parameterMatch.Add(definedQueryMethod.Parameters[i], onMember.Arguments[i - diff]); } } var body = definedQueryMethod.Body.Transform(parameterMatch, static (parameterMatch, e) =>
// Returns // (ParentType p) => dc.GetTable<ObjectType>().Where(...) // (ParentType p) => dc.GetTable<ObjectType>().Where(...).DefaultIfEmpty public static LambdaExpression CreateAssociationQueryLambda(ExpressionBuilder builder, AccessorMember onMember, AssociationDescriptor association, Type parentOriginalType, Type parentType, Type objectType, bool inline, bool enforceDefault, List <LoadWithInfo[]>?loadWith, out bool isLeft) { var dataContextConstant = Expression.Constant(builder.DataContext, builder.DataContext.GetType()); // We are trying to keep fast cache hit behaviour, so cache check should be added only if needed // bool shouldAddCacheCheck = false; bool cacheCheckAdded = false; LambdaExpression?definedQueryMethod = null; if (association.HasQueryMethod()) { // here we tell for Expression Comparer to compare optimized Association expressions // definedQueryMethod = (LambdaExpression)builder.AddQueryableMemberAccessors(onMember, builder.DataContext, (mi, dc) => { var queryLambda = association.GetQueryMethod(parentType, objectType) ?? throw new InvalidOperationException(); var optimizationContext = new ExpressionTreeOptimizationContext(dc); var optimizedExpr = optimizationContext.ExposeExpression(queryLambda); optimizedExpr = optimizationContext.ExpandQueryableMethods(optimizedExpr); optimizedExpr = optimizedExpr.OptimizeExpression() !; return(optimizedExpr); }); cacheCheckAdded = true; var parameterMatch = new Dictionary <ParameterExpression, Expression>(); if (onMember.Arguments == null) { if (definedQueryMethod.Parameters.Count > 1 && typeof(IDataContext).IsSameOrParentOf(definedQueryMethod.Parameters[1].Type)) { parameterMatch.Add(definedQueryMethod.Parameters[1], dataContextConstant); } } else { var definedCount = definedQueryMethod.Parameters.Count; var argumentsCount = onMember.Arguments.Count; var diff = definedCount - argumentsCount; for (int i = definedCount - 1; i >= diff; i--) { parameterMatch.Add(definedQueryMethod.Parameters[i], onMember.Arguments[i - diff]); } } var body = definedQueryMethod.Body.Transform(e => { if (e.NodeType == ExpressionType.Parameter && parameterMatch.TryGetValue((ParameterExpression)e, out var newExpression)) { return(newExpression); } return(e); }); definedQueryMethod = Expression.Lambda(body, definedQueryMethod.Parameters[0]); } var shouldAddDefaultIfEmpty = enforceDefault; if (definedQueryMethod == null) { var parentParam = Expression.Parameter(parentType, "parent"); var childParam = Expression.Parameter(objectType, association.AliasName); var parentAccessor = TypeAccessor.GetAccessor(parentType); var childAccessor = TypeAccessor.GetAccessor(objectType); Expression?predicate = null; for (var i = 0; i < association.ThisKey.Length; i++) { var parentName = association.ThisKey[i]; var parentMember = parentAccessor.Members.Find(m => m.MemberInfo.Name == parentName); if (parentMember == null) { throw new LinqException("Association key '{0}' not found for type '{1}.", parentName, parentType); } var childName = association.OtherKey[i]; var childMember = childAccessor.Members.Find(m => m.MemberInfo.Name == childName); if (childMember == null) { throw new LinqException("Association key '{0}' not found for type '{1}.", childName, objectType); } var current = ExpressionBuilder.Equal(builder.MappingSchema, Expression.MakeMemberAccess(parentParam, parentMember.MemberInfo), Expression.MakeMemberAccess(childParam, childMember.MemberInfo)); predicate = predicate == null ? current : Expression.AndAlso(predicate, current); } var expressionPredicate = association.GetPredicate(parentType, objectType); if (expressionPredicate != null) { shouldAddDefaultIfEmpty = true; shouldAddCacheCheck = true; var replacedBody = expressionPredicate.GetBody(parentParam, childParam); predicate = predicate == null ? replacedBody : Expression.AndAlso(predicate, replacedBody); } if (predicate == null) { throw new LinqException("Can not generate Association predicate"); } if (inline && !shouldAddDefaultIfEmpty) { var ed = builder.MappingSchema.GetEntityDescriptor(objectType); if (ed.QueryFilterFunc != null) { shouldAddDefaultIfEmpty = true; shouldAddCacheCheck = true; } } var queryParam = Expression.Call(Methods.LinqToDB.GetTable.MakeGenericMethod(objectType), dataContextConstant); var filterLambda = Expression.Lambda(predicate, childParam); Expression body = Expression.Call(Methods.Queryable.Where.MakeGenericMethod(objectType), queryParam, filterLambda); definedQueryMethod = Expression.Lambda(body, parentParam); } else { shouldAddDefaultIfEmpty = true; var bodyExpression = definedQueryMethod.Body.Unwrap(); if (bodyExpression.NodeType == ExpressionType.Call) { var mc = (MethodCallExpression)bodyExpression; if (mc.IsSameGenericMethod(Methods.Queryable.DefaultIfEmpty, Methods.Queryable.DefaultIfEmptyValue)) { shouldAddDefaultIfEmpty = false; } } } if (!cacheCheckAdded && shouldAddCacheCheck) { // here we tell for Expression Comparer to compare optimized Association expressions // var closureExpr = definedQueryMethod; definedQueryMethod = (LambdaExpression)builder.AddQueryableMemberAccessors(onMember, builder.DataContext, (mi, dc) => { var optimizationContext = new ExpressionTreeOptimizationContext(dc); var optimizedExpr = optimizationContext.ExposeExpression(closureExpr); optimizedExpr = optimizationContext.ExpandQueryableMethods(optimizedExpr); optimizedExpr = optimizedExpr.OptimizeExpression() !; return(optimizedExpr); }); } if (loadWith != null) { var associationLoadWith = GetLoadWith(loadWith)? .FirstOrDefault(li => li.Info.MemberInfo == association.MemberInfo); if (associationLoadWith != null && (associationLoadWith.Info.MemberFilter != null || associationLoadWith.Info.FilterFunc != null)) { var body = definedQueryMethod.Body.Unwrap(); var memberFilter = associationLoadWith.Info.MemberFilter; if (memberFilter != null) { var elementType = EagerLoading.GetEnumerableElementType(memberFilter.Parameters[0].Type, builder.MappingSchema); var filtered = Expression.Convert(body, typeof(IEnumerable <>).MakeGenericType(elementType)); var filterBody = memberFilter.GetBody(filtered); body = Expression.Call( Methods.Enumerable.AsQueryable.MakeGenericMethod(objectType), filterBody); } var loadWithFunc = associationLoadWith.Info.FilterFunc; if (loadWithFunc != null) { loadWithFunc = loadWithFunc.Unwrap(); if (loadWithFunc is LambdaExpression lambda) { body = lambda.GetBody(body); } else { var filterDelegate = loadWithFunc.EvaluateExpression <Delegate>() ?? throw new LinqException("Cannot convert filter function '{loadWithFunc}' to Delegate."); var arumentType = filterDelegate.GetType().GetGenericArguments()[0].GetGenericArguments()[0]; // check for fake argument q => q if (arumentType.IsSameOrParentOf(objectType)) { var query = ExpressionQueryImpl.CreateQuery(objectType, builder.DataContext, body); var filtered = (IQueryable)filterDelegate.DynamicInvoke(query) !; body = filtered.Expression; } } } definedQueryMethod = Expression.Lambda(body, definedQueryMethod.Parameters); } if (associationLoadWith?.NextLoadWith != null && associationLoadWith.NextLoadWith.Count > 0) { definedQueryMethod = (LambdaExpression)EnrichTablesWithLoadWith(builder.DataContext, definedQueryMethod, objectType, associationLoadWith.NextLoadWith, builder.MappingSchema); } } if (parentOriginalType != parentType) { // add discriminator filter var ed = builder.MappingSchema.GetEntityDescriptor(parentOriginalType); foreach (var inheritanceMapping in ed.InheritanceMapping) { if (inheritanceMapping.Type == parentType) { var objParam = Expression.Parameter(objectType, "o"); var filterLambda = Expression.Lambda(ExpressionBuilder.Equal(builder.MappingSchema, Expression.MakeMemberAccess(definedQueryMethod.Parameters[0], inheritanceMapping.Discriminator.MemberInfo), Expression.Constant(inheritanceMapping.Code)), objParam); var body = definedQueryMethod.Body.Unwrap(); body = Expression.Call(Methods.Queryable.Where.MakeGenericMethod(objectType), body, filterLambda); definedQueryMethod = Expression.Lambda(body, definedQueryMethod.Parameters); shouldAddDefaultIfEmpty = true; break; } } } if (inline && shouldAddDefaultIfEmpty) { var body = definedQueryMethod.Body.Unwrap(); body = Expression.Call(Methods.Queryable.DefaultIfEmpty.MakeGenericMethod(objectType), body); definedQueryMethod = Expression.Lambda(body, definedQueryMethod.Parameters); isLeft = true; } else { isLeft = false; } definedQueryMethod = (LambdaExpression)builder.ConvertExpressionTree(definedQueryMethod); definedQueryMethod = (LambdaExpression)builder.ConvertExpression(definedQueryMethod); definedQueryMethod = (LambdaExpression)definedQueryMethod.OptimizeExpression() !; return(definedQueryMethod); }