static Expression GetMultipleQueryExpression(IBuildContext context, MappingSchema mappingSchema, Expression expression, HashSet <ParameterExpression> parameters, out bool isLazy) { var valueExpression = EagerLoading.GenerateDetailsExpression(context, mappingSchema, expression); if (valueExpression == null) { isLazy = true; return(GetMultipleQueryExpressionLazy(context, mappingSchema, expression, parameters)); } valueExpression = EagerLoading.AdjustType(valueExpression, expression.Type, mappingSchema); isLazy = false; return(valueExpression); }
static void CheckFilterFunc(Type expectedType, Type filterType, MappingSchema mappingSchema) { var propType = expectedType; if (EagerLoading.IsEnumerableType(expectedType, mappingSchema)) { propType = EagerLoading.GetEnumerableElementType(expectedType, mappingSchema); } var itemType = typeof(Expression <>).IsSameOrParentOf(filterType) ? filterType.GetGenericArguments()[0].GetGenericArguments()[0].GetGenericArguments()[0] : filterType.GetGenericArguments()[0].GetGenericArguments()[0]; if (propType != itemType) { throw new LinqException("Invalid filter function usage."); } }
static Expression GetMultipleQueryExpression(IBuildContext context, MappingSchema mappingSchema, Expression expression, HashSet <ParameterExpression> parameters, out bool isLazy) { if (!Common.Configuration.Linq.AllowMultipleQuery) { throw new LinqException("Multiple queries are not allowed. Set the 'LinqToDB.Common.Configuration.Linq.AllowMultipleQuery' flag to 'true' to allow multiple queries."); } var valueExpression = EagerLoading.GenerateDetailsExpression(context, mappingSchema, expression, parameters); if (valueExpression == null) { isLazy = true; return(GetMultipleQueryExpressionLazy(context, mappingSchema, expression, parameters)); } valueExpression = EagerLoading.EnsureDestinationType(valueExpression, expression.Type, mappingSchema); isLazy = false; return(valueExpression); }
public Type GetElementType(MappingSchema mappingSchema) { var type = MemberInfo.GetMemberType(); return(EagerLoading.GetEnumerableElementType(type, mappingSchema)); }
public static Expression EnrichLoadWith(IDataContext dataContext, Expression table, Type entityType, List <LoadWithInfo[]> loadWith, MappingSchema mappingSchema) { var args = new List <Expression>(2); var currentObj = table; foreach (var members in loadWith) { var currentEntityType = entityType; var isPrevEnumerable = false; Type?prevMemberType = null; foreach (var member in members) { args.Clear(); args.Add(currentObj); var memberType = member.MemberInfo.GetMemberType(); var isEnumerableMember = EagerLoading.IsEnumerableType(memberType, mappingSchema); var desiredType = member.MemberInfo.IsMethodEx() ? currentEntityType : member.MemberInfo.DeclaringType; var entityParam = Expression.Parameter(currentEntityType, "e"); var loadBody = desiredType == currentEntityType ? (Expression)entityParam : Expression.Convert(entityParam, desiredType); loadBody = Expression.MakeMemberAccess(loadBody, member.MemberInfo); if (member.MemberFilter != null) { loadBody = member.MemberFilter.GetBody(loadBody); } var hasFilterFunc = member.FilterFunc != null; if (isEnumerableMember && hasFilterFunc) { var propType = EagerLoading.GetEnumerableElementType(memberType, mappingSchema); var enumerableType = typeof(IEnumerable <>).MakeGenericType(propType); if (loadBody.Type != enumerableType) { loadBody = Expression.Convert(loadBody, enumerableType); } } args.Add(Expression.Quote(Expression.Lambda(loadBody, entityParam))); if (hasFilterFunc) { args.Add(member.FilterFunc !); } MethodInfo method; if (prevMemberType == null) { method = !hasFilterFunc ? Methods.LinqToDB.LoadWith : isEnumerableMember ? Methods.LinqToDB.LoadWithManyFilter : Methods.LinqToDB.LoadWithSingleFilter; var propType = memberType; if (hasFilterFunc && isEnumerableMember) { propType = EagerLoading.GetEnumerableElementType(propType, mappingSchema); } method = method.MakeGenericMethod(entityType, propType); } else { if (isPrevEnumerable) { if (!hasFilterFunc) { method = Methods.LinqToDB.ThenLoadFromMany; } else if (isEnumerableMember) { method = Methods.LinqToDB.ThenLoadFromManyManyFilter; } else { method = Methods.LinqToDB.ThenLoadFromManySingleFilter; } } else { if (!hasFilterFunc) { method = Methods.LinqToDB.ThenLoadFromSingle; } else if (isEnumerableMember) { method = Methods.LinqToDB.ThenLoadFromSingleManyFilter; } else { method = Methods.LinqToDB.ThenLoadFromSingleSingleFilter; } } var propType = memberType; if (hasFilterFunc && isEnumerableMember) { propType = EagerLoading.GetEnumerableElementType(propType, mappingSchema); } method = method.MakeGenericMethod(entityType, prevMemberType, propType); } currentObj = Expression.Call(method, args); isPrevEnumerable = isEnumerableMember && !hasFilterFunc; if (isEnumerableMember) { memberType = EagerLoading.GetEnumerableElementType(memberType, mappingSchema); } prevMemberType = memberType; currentEntityType = memberType; } } return(currentObj); }
// 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); }
SqlInfo[] ConvertToIndexInternal(Expression? expression, int level, ConvertFlags flags) { if (IsScalar) { if (Body.NodeType == ExpressionType.Parameter) for (var i = 0; i < Sequence.Length; i++) if (Body == Lambda.Parameters[i]) return Sequence[i].ConvertToIndex(expression, level, flags); if (expression == null) { var key = Tuple.Create((MemberInfo?)null, flags); if (!_memberIndex.TryGetValue(key, out var idx)) { idx = ConvertToSql(null, 0, flags); foreach (var info in idx) SetInfo(info, null); _memberIndex.Add(key, idx); } return idx; } switch (flags) { case ConvertFlags.Field : case ConvertFlags.Key : case ConvertFlags.All : return ProcessScalar( expression, level, (ctx, ex, l) => ctx.ConvertToIndex(ex, l, flags), () => GetSequence(expression, level)!.ConvertToIndex(expression, level + 1, flags)); } } else { if (expression == null) { switch (flags) { case ConvertFlags.Field : case ConvertFlags.Key : case ConvertFlags.All : { var p = Expression.Parameter(Body.Type, "p"); var q = from m in Members.Keys where !(m is MethodInfo || EagerLoading.IsDetailsMember(m)) select new SqlData { Sql = ConvertToIndex(Expression.MakeMemberAccess(p, m), 1, flags), Member = m } into mm from m in mm.Sql.Select(s => s.Clone(mm.Member)) select m; return q.ToArray(); } } } switch (flags) { case ConvertFlags.All : case ConvertFlags.Key : case ConvertFlags.Field : { if (level == 0) { var idx = Builder.ConvertExpressions(this, expression!, flags); foreach (var info in idx) SetInfo(info, null); return idx; } var levelExpression = expression!.GetLevelExpression(Builder.MappingSchema, level); switch (levelExpression.NodeType) { case ExpressionType.MemberAccess : { if (levelExpression == expression) { var member = Tuple.Create((MemberInfo?)((MemberExpression)levelExpression).Member, flags); if (!_memberIndex.TryGetValue(member, out var idx)) { idx = ConvertToSql(expression, level, flags); if (flags == ConvertFlags.Field && idx.Length != 1) throw new InvalidOperationException(); foreach (var info in idx) SetInfo(info, member.Item1); _memberIndex.Add(member, idx); } return idx; } return ProcessMemberAccess( expression!, (MemberExpression)levelExpression, level, (n, ctx, ex, l, _) => n == 0 ? GetSequence(expression!, level)!.ConvertToIndex(expression, level + 1, flags) : ctx.ConvertToIndex(ex, l, flags)); } case ExpressionType.Parameter: case ExpressionType.Extension: if (levelExpression != expression) return GetSequence(expression!, level)!.ConvertToIndex(expression, level + 1, flags); break; } break; } } } throw new NotImplementedException(); }
public virtual SqlInfo[] ConvertToSql(Expression? expression, int level, ConvertFlags flags) { if (expression != null && level > 0 && expression.NodeType == ExpressionType.Call) { var e = (MethodCallExpression)expression; if (e.Method.DeclaringType == typeof(Enumerable) && !typeof(IGrouping<,>).IsSameOrParentOf(e.Arguments[0].Type)) { return new[] { new SqlInfo { Sql = Builder.SubQueryToSql(this, e) } }; } } if (IsScalar) { if (expression == null) return Builder.ConvertExpressions(this, Body, flags); switch (flags) { case ConvertFlags.Field : case ConvertFlags.Key : case ConvertFlags.All : { if (Body.NodeType != ExpressionType.Parameter && level == 0) { var levelExpression = expression.GetLevelExpression(Builder.MappingSchema, level); if (levelExpression != expression) if (flags != ConvertFlags.Field && IsExpression(expression, level, RequestFor.Field).Result) flags = ConvertFlags.Field; } return ProcessScalar( expression, level, (ctx, ex, l) => ctx.ConvertToSql(ex, l, flags), () => new[] { new SqlInfo { Sql = Builder.ConvertToSql(this, expression) } }); } } } else { if (expression == null) { if (flags != ConvertFlags.Field) { var q = from m in Members where !(m.Key is MethodInfo || EagerLoading.IsDetailsMember(m.Key)) select ConvertMember(m.Key, m.Value, flags) into mm from m in mm select m; return q.ToArray(); } throw new NotImplementedException(); } switch (flags) { case ConvertFlags.All : case ConvertFlags.Key : case ConvertFlags.Field : { var levelExpression = expression.GetLevelExpression(Builder.MappingSchema, level); levelExpression = levelExpression.Unwrap(); switch (levelExpression.NodeType) { case ExpressionType.MemberAccess : { if (level != 0 && levelExpression == expression) { var member = ((MemberExpression)levelExpression).Member; if (!_sql.TryGetValue(member, out var sql)) { var memberExpression = GetMemberExpression( member, levelExpression == expression, levelExpression.Type, expression); sql = ConvertExpressions(memberExpression, flags) .Select(si => si.Clone(member)).ToArray(); _sql.Add(member, sql); } return sql; } return ProcessMemberAccess( expression, (MemberExpression)levelExpression, level, (n,ctx,ex,l,mex) => { switch (n) { case 0 : var buildExpression = GetExpression(expression, levelExpression, mex); return ConvertExpressions(buildExpression, flags); default: return ctx.ConvertToSql(ex, l, flags); } }); } case ExpressionType.Parameter: if (levelExpression != expression) return GetSequence(expression, level)!.ConvertToSql(expression, level + 1, flags); if (level == 0) return GetSequence(expression, level)!.ConvertToSql(null, 0, flags); break; case ExpressionType.Extension: { if (levelExpression is ContextRefExpression) { if (levelExpression != expression) return GetSequence(expression, level)!.ConvertToSql(expression, level + 1, flags); if (level == 0) return GetSequence(expression, level)!.ConvertToSql(null, 0, flags); } goto default; } default: if (level == 0) return Builder.ConvertExpressions(this, expression, flags); break; } break; } } } throw new NotImplementedException(); }
public GetItemExpression(Expression expression, MappingSchema mappingSchema) { Expression = expression; _type = EagerLoading.GetEnumerableElementType(expression.Type, mappingSchema); }