public virtual IEnumerable <T> ExecuteSubQuery <T>(LambdaExpression query, IDataReader dataReader) { var projection = (SqlProjectionExpression)query.Body; var expectedSelector = "GROUPBYCOLUMNS-" + projection.Select.Alias; projection = (SqlProjectionExpression)SqlExpressionReplacer.Replace(projection, c => { if (query.Parameters[0] == c) { return(Expression.Constant(this)); } var column = c as SqlColumnExpression; if (column != null && column.SelectAlias.StartsWith(expectedSelector)) { var sqlDataTypeProvider = this.SqlDatabaseContext.SqlDataTypeProvider.GetSqlDataType(column.Type); var parameter = Expression.Parameter(typeof(IDataReader)); var func = Expression.Lambda <Func <IDataReader, object> >(Expression.Convert(sqlDataTypeProvider.GetReadExpression(parameter, dataReader.GetOrdinal(column.Name)), typeof(object)), parameter).Compile(); var value = func(dataReader); return(Expression.Constant(value, column.Type)); } return(null); }); projection = (SqlProjectionExpression)SqlQueryProvider.Optimize(projection, this.SqlDatabaseContext.SqlDataTypeProvider.GetTypeForEnums(), true); return(this.provider.CreateQuery <T>(projection)); }
protected Expression RewriteSelectManyProjection(MethodCallExpression methodCallExpression) { var outer = this.Visit(methodCallExpression.Arguments[0]); var collection = this.Visit(methodCallExpression.Arguments[1]); var resultSelector = methodCallExpression.Arguments[2].StripQuotes(); var originalSelectorA = resultSelector.StripQuotes().Parameters[0]; var originalSelectorB = resultSelector.StripQuotes().Parameters[1]; var newA = Expression.Parameter(originalSelectorA.Type); var newB = Expression.Parameter(originalSelectorB.Type); var resultValue = Expression.Parameter(typeof(ExpandedJoinSelectKey <,>).MakeGenericType(originalSelectorA.Type, originalSelectorB.Type)); var newResultSelector = Expression.Lambda(Expression.MemberInit(resultValue.Type.CreateNewExpression(), Expression.Bind(resultValue.Type.GetProperty("Outer"), newA), Expression.Bind(resultValue.Type.GetProperty("Inner"), newB)), newA, newB); var newSelectMany = Expression.Call(null, MethodInfoFastRef.QueryableSelectManyMethod.MakeGenericMethod(methodCallExpression.Method.GetGenericArguments()[0], methodCallExpression.Method.GetGenericArguments()[1], newResultSelector.ReturnType), outer, collection, newResultSelector); var selectorParameter = Expression.Parameter(resultValue.Type); var selectProjectorBody = SqlExpressionReplacer.Replace(resultSelector.Body, originalSelectorA, Expression.Property(selectorParameter, "Outer")); selectProjectorBody = SqlExpressionReplacer.Replace(selectProjectorBody, originalSelectorB, Expression.Property(selectorParameter, "Inner")); var selectProjector = Expression.Lambda(selectProjectorBody, selectorParameter); var select = Expression.Call(null, MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod(selectorParameter.Type, selectProjector.ReturnType), newSelectMany, selectProjector); return(select); }
/// <summary> /// Translates Joins from type (1) to type (2) /// 1) Join(outer, inner, c => c.outerKey, c => d.innerKey, (x, y) => new { x.a.b.c, y.a.b.c }) /// 2) Join(outer, inner, c => c.outerKey, c => d.innerKey, (x, y) => new { x, y }).Select(c => new { c.x.a.b.c, c.y.a.b.v }) /// </summary> protected Expression RewriteExplicitJoinProjection(MethodCallExpression methodCallExpression) { var outer = this.Visit(methodCallExpression.Arguments[0]); var inner = this.Visit(methodCallExpression.Arguments[1]); var outerKeySelector = methodCallExpression.Arguments[2].StripQuotes(); var innerKeySelector = methodCallExpression.Arguments[3].StripQuotes(); var resultSelector = methodCallExpression.Arguments[4].StripQuotes(); var originalOuterKeyParam = resultSelector.StripQuotes().Parameters[0]; var originalInnerKeyParam = resultSelector.StripQuotes().Parameters[1]; var outerKey = Expression.Parameter(outerKeySelector.Parameters[0].Type); var innerKey = Expression.Parameter(innerKeySelector.Parameters[0].Type); var resultValue = Expression.Parameter(typeof(ExpandedJoinSelectKey <,>).MakeGenericType(outerKey.Type, innerKey.Type)); var newResultSelector = Expression.Lambda(Expression.MemberInit(resultValue.Type.CreateNewExpression(), Expression.Bind(resultValue.Type.GetProperty("Outer"), outerKey), Expression.Bind(resultValue.Type.GetProperty("Inner"), innerKey)), outerKey, innerKey); var newJoin = Expression.Call(null, MethodInfoFastRef.QueryableJoinMethod.MakeGenericMethod(outer.Type.GetSequenceElementType() ?? outer.Type, inner.Type.GetSequenceElementType() ?? inner.Type, outerKeySelector.ReturnType, newResultSelector.ReturnType), outer, inner, outerKeySelector, innerKeySelector, newResultSelector); var selectorParameter = Expression.Parameter(resultValue.Type); var selectProjectorBody = SqlExpressionReplacer.Replace(resultSelector.Body, originalOuterKeyParam, Expression.Property(selectorParameter, "Outer")); selectProjectorBody = SqlExpressionReplacer.Replace(selectProjectorBody, originalInnerKeyParam, Expression.Property(selectorParameter, "Inner")); var selectProjector = Expression.Lambda(selectProjectorBody, selectorParameter); var select = Expression.Call(null, MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod(selectorParameter.Type, selectProjector.ReturnType), newJoin, selectProjector); return(select); }
private static MethodCallExpression MakeJoinCallExpression(int index, Expression left, Expression right, PropertyPath targetPath, Dictionary <PropertyPath, int> indexByPath, Dictionary <PropertyPath, Expression> rootExpressionsByPath, Expression sourceParameterExpression) { Expression leftObject; var leftElementType = left.Type.GetGenericArguments()[0]; var rightElementType = right.Type.GetGenericArguments()[0]; var rootPath = targetPath.PathWithoutLast(); var leftSelectorParameter = Expression.Parameter(leftElementType); if (index == 1 && rootExpressionsByPath.ContainsKey(rootPath)) { leftObject = rootExpressionsByPath[rootPath]; leftObject = SqlExpressionReplacer.Replace(leftObject, c => { if (c == sourceParameterExpression) { return(leftSelectorParameter); } return(null); }); } else { leftObject = CreateExpressionForPath(index - 1, rootPath, leftSelectorParameter, indexByPath); if (rootExpressionsByPath.ContainsKey(rootPath)) { foreach (var property in rootPath) { leftObject = Expression.Property(leftObject, property.Name); } } } var leftSelector = Expression.Lambda(Expression.Property(leftObject, targetPath.Last().Name), leftSelectorParameter); var rightSelectorParameter = Expression.Parameter(rightElementType); var rightSelector = Expression.Lambda(rightSelectorParameter, rightSelectorParameter); var projector = MakeJoinProjector(leftElementType, rightElementType); var method = JoinHelperExtensions.LeftJoinMethod.MakeGenericMethod(leftElementType, rightElementType, targetPath.Last().GetMemberReturnType(), projector.ReturnType); return(Expression.Call(null, method, left, right, Expression.Quote(leftSelector), Expression.Quote(rightSelector), Expression.Quote(projector))); }
private void BuildProjector(LambdaExpression projectionLambda, LambdaExpression aggregator, out Delegate projector, out Delegate asyncProjector) { var sqlQueryProviderParam = Expression.Parameter(typeof(SqlQueryProvider)); var formatResultsParam = Expression.Parameter(typeof(SqlQueryFormatResult)); var placeholderValuesParam = Expression.Parameter(typeof(object[])); var elementType = projectionLambda.ReturnType; Expression executor; if (elementType.IsDataAccessObjectType()) { var concreteElementType = this.DataAccessModel.GetConcreteTypeFromDefinitionType(elementType); var constructor = typeof(DataAccessObjectProjector <,>).MakeGenericType(elementType, concreteElementType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } else { if ((aggregator?.Body as MethodCallExpression)?.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.EnumerableExtensionsAlwaysReadFirstMethod) { var constructor = typeof(AlwaysReadFirstObjectProjector <,>).MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } else { var projectorType = !DataAccessObjectAwareResultTypeComparerBuilder.NeedsComparer(projectionLambda.ReturnType) ? typeof(ObjectProjector <,>) : typeof(DataAccessObjectContainerProjector <,>); var constructor = projectorType.MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } } var asyncExecutor = executor; var cancellationToken = Expression.Parameter(typeof(CancellationToken)); if (aggregator != null) { var originalExecutor = executor; var aggr = aggregator; var newBody = SqlConstantPlaceholderReplacer.Replace(aggr.Body, placeholderValuesParam); executor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor); newBody = ProjectionAsyncRewriter.Rewrite(newBody, cancellationToken); asyncExecutor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor); } projectionLambda = Expression.Lambda(executor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam); var asyncProjectorLambda = Expression.Lambda(asyncExecutor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam, cancellationToken); ProjectorCacheInfo cacheInfo; var key = new ProjectorCacheKey(projectionLambda); var oldCache = this.SqlDatabaseContext.projectorCache; if (!oldCache.TryGetValue(key, out cacheInfo)) { cacheInfo.projector = projectionLambda.Compile(); cacheInfo.asyncProjector = asyncProjectorLambda.Compile(); if (this.SqlDatabaseContext.projectorCache.Count >= ProjectorCacheMaxLimit) { ProjectionExpressionCacheLogger.Info(() => $"Projector has been flushed because it overflowed with a size of {ProjectionExpressionCacheMaxLimit}\n\nProjector: {projectionLambda}\n\nAt: {new StackTrace()}"); var newCache = new Dictionary <ProjectorCacheKey, ProjectorCacheInfo>(ProjectorCacheMaxLimit, ProjectorCacheEqualityComparer.Default); foreach (var value in oldCache.Take(oldCache.Count / 3)) { newCache[value.Key] = value.Value; } newCache[key] = cacheInfo; this.SqlDatabaseContext.projectorCache = newCache; } else { var newCache = new Dictionary <ProjectorCacheKey, ProjectorCacheInfo>(oldCache, ProjectorCacheEqualityComparer.Default) { [key] = cacheInfo }; this.SqlDatabaseContext.projectorCache = newCache; } ProjectionCacheLogger.Info(() => $"Cached projector:\n{cacheInfo.projector}"); ProjectionCacheLogger.Debug(() => $"Projector Cache Size: {this.SqlDatabaseContext.projectionExpressionCache.Count}"); } projector = cacheInfo.projector; asyncProjector = cacheInfo.asyncProjector; }
protected Expression RewriteBasicProjection(MethodCallExpression methodCallExpression, bool forSelector) { Expression[] originalSelectors; var originalSource = methodCallExpression.Arguments[0]; var source = this.Visit(originalSource); var sourceType = source.Type.GetGenericArguments()[0]; var originalPredicateOrSelector = methodCallExpression.Arguments[1]; if (methodCallExpression.Arguments.Count == 2) { originalSelectors = new[] { originalPredicateOrSelector }; } else { originalSelectors = new[] { originalPredicateOrSelector, methodCallExpression.Arguments[2] }; } var sourceParameterExpression = (originalPredicateOrSelector.StripQuotes()).Parameters[0]; var result = ReferencedRelatedObjectPropertyGatherer.Gather(this.model, originalSelectors, sourceParameterExpression, forSelector); var memberAccessExpressionsNeedingJoins = result.ReferencedRelatedObjectByPath; var currentRootExpressionsByPath = result.RootExpressionsByPath; var predicateOrSelectors = result.ReducedExpressions; var predicateOrSelectorLambdas = predicateOrSelectors.Select(c => c.StripQuotes()).ToArray(); if (memberAccessExpressionsNeedingJoins.Count > 0) { var replacementExpressionForPropertyPath = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default); var referencedObjectPaths = memberAccessExpressionsNeedingJoins .OrderBy(c => c.Key.Length) .Select(c => c.Value) .ToList(); var types = referencedObjectPaths .Select(c => c.FullAccessPropertyPath.Last.PropertyType) .ToList(); var finalTupleType = CreateFinalTupleType(sourceType, types); var replacementExpressionsByPropertyPathForSelector = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default); var parameter = Expression.Parameter(finalTupleType); var i = 1; var indexByPath = new Dictionary <PropertyPath, int>(PropertyPathEqualityComparer.Default); foreach (var value in referencedObjectPaths) { indexByPath[value.FullAccessPropertyPath] = i++; } indexByPath[PropertyPath.Empty] = 0; foreach (var x in currentRootExpressionsByPath) { indexByPath[x.Key] = 0; } foreach (var path in referencedObjectPaths.Select(c => c.FullAccessPropertyPath)) { var replacement = CreateExpressionForPath(referencedObjectPaths.Count, path, parameter, indexByPath); replacementExpressionsByPropertyPathForSelector[path] = replacement; } replacementExpressionsByPropertyPathForSelector[PropertyPath.Empty] = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, parameter, indexByPath); foreach (var value in replacementExpressionsByPropertyPathForSelector) { replacementExpressionForPropertyPath[value.Key] = value.Value; } var propertyPathsByOriginalExpression = referencedObjectPaths .SelectMany(d => d.TargetExpressions.Select(e => new { PropertyPath = d.FullAccessPropertyPath, Expression = e })) .ToDictionary(c => c.Expression, c => c.PropertyPath); foreach (var lambda in predicateOrSelectorLambdas) { propertyPathsByOriginalExpression[lambda.Parameters[0]] = PropertyPath.Empty; } var replacementExpressions = propertyPathsByOriginalExpression .ToDictionary(c => c.Key, c => replacementExpressionsByPropertyPathForSelector[c.Value]); var index = 1; var currentLeft = source; foreach (var referencedObjectPath in referencedObjectPaths) { var property = referencedObjectPath.FullAccessPropertyPath[referencedObjectPath.FullAccessPropertyPath.Length - 1]; var right = Expression.Constant(this.model.GetDataAccessObjects(property.PropertyType), typeof(DataAccessObjects <>).MakeGenericType(property.PropertyType)); var join = MakeJoinCallExpression(index, currentLeft, right, referencedObjectPath.FullAccessPropertyPath, indexByPath, currentRootExpressionsByPath, sourceParameterExpression); currentLeft = join; index++; } Func <Expression, bool, Expression> replace = null; replace = (e, b) => SqlExpressionReplacer.Replace(e, c => { Expression value; if (forSelector && b) { if (result.IncludedPropertyInfoByExpression.ContainsKey(c)) { var x = replace(c, false); var y = result.IncludedPropertyInfoByExpression[c]; var newList = y.Select(includedPropertyInfo => new IncludedPropertyInfo { RootExpression = x, FullAccessPropertyPath = includedPropertyInfo.FullAccessPropertyPath, IncludedPropertyPath = includedPropertyInfo.IncludedPropertyPath }).ToList(); this.includedPropertyInfos[x] = newList; return(x); } } if (replacementExpressions.TryGetValue(c, out value)) { return(value); } return(null); }); var newPredicatorOrSelectorBodies = predicateOrSelectorLambdas.Select(c => replace(c.Body, true)).ToArray(); var newPredicateOrSelectors = newPredicatorOrSelectorBodies.Select(c => Expression.Lambda(c, parameter)).ToArray(); MethodInfo newMethod; MethodCallExpression newCall; var newParameterType = newPredicateOrSelectors[0].Parameters[0].Type; if (methodCallExpression.Method.Name.StartsWith("Select") || methodCallExpression.Method.Name.StartsWith("Where") || methodCallExpression.Method.Name.EqualsIgnoreCase("OrderBy")) { if (methodCallExpression.Method.Name.StartsWith("Select")) { var projectionResultType = newPredicateOrSelectors[0].ReturnType; newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType); newCall = Expression.Call(null, newMethod, new[] { currentLeft, newPredicateOrSelectors[0] }); } else if (methodCallExpression.Method.Name.StartsWith("Where")) { newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType); newCall = Expression.Call(null, newMethod, new[] { currentLeft, newPredicateOrSelectors[0] }); } else if (methodCallExpression.Method.Name.StartsWith("OrderBy")) { var keyType = newPredicateOrSelectors[0].ReturnType; newMethod = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, keyType); newCall = Expression.Call(null, newMethod, new[] { currentLeft, newPredicateOrSelectors[0] }); } else { throw new InvalidOperationException(); } if (newCall.Method.ReturnType.GetGenericArguments()[0].IsGenericType && newCall.Method.ReturnType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(LeftRightJoinInfo <,>)) { var selectParameter = Expression.Parameter(newCall.Method.ReturnType.GetGenericArguments()[0]); var selectBody = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, selectParameter, indexByPath); var selectCall = Expression.Lambda(selectBody, selectParameter); var selectMethod = MethodInfoFastRef.QueryableSelectMethod.MakeGenericMethod ( selectParameter.Type, selectCall.ReturnType ); newCall = Expression.Call(null, selectMethod, new Expression[] { newCall, selectCall }); } this.replacementExpressionForPropertyPathsByJoin.Add(new Tuple <Expression, Dictionary <PropertyPath, Expression> >(newCall, replacementExpressionForPropertyPath)); } else if (methodCallExpression.Method.Name == ("GroupBy")) { var keyType = newPredicateOrSelectors[0].ReturnType; var elementType = methodCallExpression.Method.ReturnType.GetGenericArguments()[0].GetGenericArguments()[1]; newMethod = methodCallExpression.Method .DeclaringType .GetMethods().Single(c => c.IsGenericMethod && c.GetGenericArguments().Length == 3 && c.GetParameters().Length == 3 && c.GetParameters()[1].ParameterType.IsGenericType && c.GetParameters()[2].ParameterType.IsGenericType && c.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression <>) && c.GetParameters()[2].ParameterType.GetGenericTypeDefinition() == typeof(Expression <>) && c.GetParameters()[1].ParameterType.GetGenericArguments()[0].IsGenericType && c.GetParameters()[2].ParameterType.GetGenericArguments()[0].IsGenericType && c.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func <,>) && c.GetParameters()[2].ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func <,>)) .MakeGenericMethod(newParameterType, keyType, elementType); var elementSelectorParameter = Expression.Parameter(newParameterType); var elementSelectorBody = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, indexByPath); var elementSelector = Expression.Lambda(elementSelectorBody, elementSelectorParameter); newCall = Expression.Call(null, newMethod, new [] { currentLeft, newPredicateOrSelectors[0], elementSelector }); } else { throw new InvalidOperationException("Method: " + methodCallExpression.Method); } this.replacementExpressionForPropertyPathsByJoin.Add(new Tuple <Expression, Dictionary <PropertyPath, Expression> >(newCall, replacementExpressionForPropertyPath)); return(newCall); } else { if (source == originalSource && predicateOrSelectors.SequenceEqual(originalSelectors, ObjectReferenceIdentityEqualityComparer <Expression> .Default)) { return(methodCallExpression); } else { return(Expression.Call ( methodCallExpression.Object, methodCallExpression.Method, predicateOrSelectors.Prepend(source).ToArray() )); } } }
private void BuildProjector(LambdaExpression projectionLambda, LambdaExpression aggregator, Expression <Func <IDataReader, object[]> > keyBuilder, out Delegate projector, out Delegate asyncProjector) { var sqlQueryProviderParam = Expression.Parameter(typeof(SqlQueryProvider)); var formatResultsParam = Expression.Parameter(typeof(SqlQueryFormatResult)); var placeholderValuesParam = Expression.Parameter(typeof(object[])); var elementType = projectionLambda.ReturnType; Expression executor; if (elementType.IsDataAccessObjectType()) { var concreteElementType = this.DataAccessModel.GetConcreteTypeFromDefinitionType(elementType); var constructor = typeof(DataAccessObjectProjector <,>) .MakeGenericType(elementType, concreteElementType) .GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } else { if ((aggregator?.Body as MethodCallExpression)?.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.EnumerableExtensionsAlwaysReadFirstMethod) { var constructor = typeof(AlwaysReadFirstObjectProjector <,>).MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType).GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } else { if (keyBuilder == null) { var constructor = typeof(ObjectProjector <,>) .MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType) .GetConstructors(BindingFlags.Public | BindingFlags.Instance) .Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda ); } else { var constructor = typeof(ComplexDataAccessObjectProjector <,>) .MakeGenericType(projectionLambda.ReturnType, projectionLambda.ReturnType) .GetConstructors(BindingFlags.Public | BindingFlags.Instance) .Single(); executor = Expression.New ( constructor, sqlQueryProviderParam, Expression.Constant(this.DataAccessModel), Expression.Constant(this.SqlDatabaseContext), formatResultsParam, placeholderValuesParam, projectionLambda, keyBuilder ); } } } var asyncExecutor = executor; var cancellationToken = Expression.Parameter(typeof(CancellationToken)); if (aggregator != null) { var originalExecutor = executor; var aggr = aggregator; var newBody = SqlConstantPlaceholderReplacer.Replace(aggr.Body, placeholderValuesParam); executor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor); newBody = ProjectionAsyncRewriter.Rewrite(newBody, cancellationToken); asyncExecutor = SqlExpressionReplacer.Replace(newBody, aggr.Parameters[0], originalExecutor); } projectionLambda = Expression.Lambda(executor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam); var asyncProjectorLambda = Expression.Lambda(asyncExecutor, sqlQueryProviderParam, formatResultsParam, placeholderValuesParam, cancellationToken); var key = new ProjectorCacheKey(projectionLambda); var oldCache = this.SqlDatabaseContext.projectorCache; if (!oldCache.TryGetValue(key, out var cacheInfo)) { cacheInfo.projector = projectionLambda.Compile(); cacheInfo.asyncProjector = asyncProjectorLambda.Compile(); this.SqlDatabaseContext.projectorCache = oldCache.Clone(key, cacheInfo, "projectorCache", this.ProjectorCacheMaxLimit); ProjectionCacheLogger.Info(() => $"Cached projector:\n{projectionLambda}"); ProjectionCacheLogger.Debug(() => $"Projector Cache Size: {this.SqlDatabaseContext.projectionExpressionCache.Count}"); } projector = cacheInfo.projector; asyncProjector = cacheInfo.asyncProjector; }
protected RewriteBasicProjectionResults RewriteBasicProjection(Expression originalSource, Tuple <LambdaExpression, ParameterExpression>[] originalSelectors, bool forProjection) { var source = Visit(originalSource); var sourceType = source.Type.GetGenericArguments()[0]; var result = SqlReferencedRelatedObjectPropertyGatherer.Gather(this.model, originalSelectors.Select(c => new Tuple <ParameterExpression, Expression>(c.Item2, c.Item1)).ToList(), forProjection); var memberAccessExpressionsNeedingJoins = result.ReferencedRelatedObjects; var currentRootExpressionsByPath = result.RootExpressionsByPath; var predicateOrSelectors = result.ReducedExpressions.Select(c => c.StripQuotes()).ToArray(); var predicateOrSelectorLambdas = predicateOrSelectors.Select(c => c.StripQuotes()).ToArray(); if (memberAccessExpressionsNeedingJoins.Count == 0) { return(new RewriteBasicProjectionResults { NewSource = source, NewSelectors = predicateOrSelectors.ToList(), ReferencedObjectPaths = new List <ReferencedRelatedObject>() }); } var replacementExpressionForPropertyPath = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default); var referencedObjectPaths = memberAccessExpressionsNeedingJoins .Where(c => !c.IncludedPropertyPath.Any(d => d.PropertyType.IsDataAccessObjectType())) .OrderBy(c => c.FullAccessPropertyPath.Length) .Concat ( memberAccessExpressionsNeedingJoins .Where(c => c.IncludedPropertyPath.Any(d => d.PropertyType.IsDataAccessObjectType())) .OrderBy(c => c.FullAccessPropertyPath.Length) ) .ToList(); var referencedPathsWithCollectionIncludes = memberAccessExpressionsNeedingJoins .Where(c => c.FullAccessPropertyPath.Last.PropertyType.GetGenericTypeDefinitionOrNull() == typeof(RelatedDataAccessObjects <>)) .Select((c, x) => new { index = x, path = c }) .OrderBy(c => c.index) .ToList(); var types = referencedObjectPaths .Select(c => c.FullAccessPropertyPath.Last.PropertyType.JoinedObjectType()) .ToList(); var finalTupleType = CreateFinalTupleType(sourceType, types); var replacementExpressionsByPropertyPathForSelector = new Dictionary <PropertyPath, Expression>(PropertyPathEqualityComparer.Default); var parameter = Expression.Parameter(finalTupleType); var i = 1; var indexByPath = new Dictionary <PropertyPath, int>(PropertyPathEqualityComparer.Default); foreach (var value in referencedObjectPaths) { indexByPath[value.FullAccessPropertyPath] = i++; } indexByPath[PropertyPath.Empty] = 0; foreach (var x in currentRootExpressionsByPath) { indexByPath[x.Key] = 0; } foreach (var path in referencedObjectPaths.Select(c => c.FullAccessPropertyPath)) { var replacement = CreateExpressionForPath(referencedObjectPaths.Count, path, parameter, indexByPath); replacementExpressionsByPropertyPathForSelector[path] = replacement; } replacementExpressionsByPropertyPathForSelector[PropertyPath.Empty] = CreateExpressionForPath(referencedObjectPaths.Count, PropertyPath.Empty, parameter, indexByPath); foreach (var value in replacementExpressionsByPropertyPathForSelector) { replacementExpressionForPropertyPath[value.Key] = value.Value; } var propertyPathsByOriginalExpression = referencedObjectPaths .SelectMany(d => d.TargetExpressions.Select(e => new { PropertyPath = d.FullAccessPropertyPath, Expression = e })) .ToDictionary(c => c.Expression, c => c.PropertyPath); foreach (var lambda in predicateOrSelectorLambdas.Select((c, idx) => new { index = idx, value = c })) { var parameterLocation = originalSelectors[lambda.index].Item1.Parameters.IndexOf(originalSelectors[lambda.index].Item2); propertyPathsByOriginalExpression[lambda.value.Parameters[parameterLocation]] = PropertyPath.Empty; } var replacementExpressions = propertyPathsByOriginalExpression .ToDictionary(c => c.Key, c => replacementExpressionsByPropertyPathForSelector[c.Value]); var index = 1; var currentLeft = source; foreach (var referencedObjectPath in referencedObjectPaths) { var property = referencedObjectPath.FullAccessPropertyPath[referencedObjectPath.FullAccessPropertyPath.Length - 1]; var right = Expression.Constant(this.model.GetDataAccessObjects(property.PropertyType.JoinedObjectType()), typeof(DataAccessObjects <>).MakeGenericType(property.PropertyType.JoinedObjectType())); var join = MakeJoinCallExpression(index, currentLeft, right, referencedObjectPath.FullAccessPropertyPath, indexByPath, currentRootExpressionsByPath, referencedObjectPath.SourceParameterExpression); currentLeft = join; index++; } Expression Replace(Expression e, bool b) => SqlExpressionReplacer.Replace(e, c => { if (forProjection && b) { if (result.IncludedPropertyInfoByExpression.ContainsKey(c)) { var x = Replace(c, false); var y = result.IncludedPropertyInfoByExpression[c]; var newList = y.Select(includedPropertyInfo => new IncludedPropertyInfo { RootExpression = x, FullAccessPropertyPath = includedPropertyInfo.FullAccessPropertyPath, IncludedPropertyPath = includedPropertyInfo.IncludedPropertyPath }).ToList(); this.includedPropertyInfos[x] = newList; return(x); } } return(replacementExpressions.GetValueOrDefault(c)); }); var newPredicatorOrSelectorBodies = predicateOrSelectorLambdas .Select(c => Replace(c.Body, true)) .ToList(); var newPredicateOrSelectors = newPredicatorOrSelectorBodies .Zip(originalSelectors, (x, y) => new { body = x, parameters = y.Item1.Parameters, sourceParameter = y.Item2 }) .Select(c => Expression.Lambda(c.body, c.parameters.Select(d => d == c.sourceParameter ? parameter : c.sourceParameter))) .ToList(); return(new RewriteBasicProjectionResults(true) { NewSource = currentLeft, IndexByPath = indexByPath, NewSelectors = newPredicateOrSelectors, ReferencedObjectPaths = referencedObjectPaths, ReplacementExpressionsByPropertyPath = replacementExpressionForPropertyPath }); }
protected Expression RewriteBasicProjection(MethodCallExpression methodCallExpression, bool forSelector) { MethodCallExpression newCall = null; var selectors = methodCallExpression .Arguments .Where(c => c.Type.GetGenericTypeDefinitionOrNull() == typeof(Expression <>) || c.Type == typeof(LambdaExpression[])) .SelectMany(c => c.Type != typeof(LambdaExpression[]) ? new [] { Visit(c).StripQuotes() } : ((LambdaExpression[])c.StripAndGetConstant().Value).Select(Visit).Cast <LambdaExpression>()) .ToArray(); var result = RewriteBasicProjection(methodCallExpression.Arguments[0], selectors.Select(c => new Tuple <LambdaExpression, ParameterExpression>(c, c.Parameters[0])).ToArray(), forSelector); if (!result.Changed) { if (result.NewSource == methodCallExpression.Arguments[0] && result.NewSelectors.SequenceEqual(selectors, ObjectReferenceIdentityEqualityComparer <Expression> .Default)) { return(base.VisitMethodCall(methodCallExpression)); } } switch (methodCallExpression.Method.Name) { case "Select": { var projectionResultType = result.NewSelectors[0].ReturnType; var newParameterType = result.NewSelectors[0].Parameters[0].Type; var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType); newCall = Expression.Call(methodWithElementSelector, new[] { result.NewSource, result.NewSelectors[0] }); break; } case "SelectMany": { if (result.NewSelectors.Count == 1) { var projectionResultType = result.NewSelectors[0].ReturnType.GetSequenceElementType(); var newParameterType = result.NewSelectors[0].Parameters[0].Type; var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, projectionResultType); newCall = Expression.Call(methodWithElementSelector, new[] { result.NewSource, result.NewSelectors[0] }); } else { var collectionType = result.NewSelectors[0].ReturnType.GetSequenceElementType(); var newParameterType = result.NewSelectors[0].Parameters[0].Type; var projectionResultType = result.NewSelectors[1].ReturnType; var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType, collectionType, projectionResultType); newCall = Expression.Call(methodWithElementSelector, new[] { result.NewSource, result.NewSelectors[0], result.NewSelectors[1] }); } break; } case "Where": { var newParameterType = result.NewSelectors[0].Parameters[0].Type; var methodWithElementSelector = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(newParameterType); newCall = Expression.Call (methodWithElementSelector, new[] { result.NewSource, result.NewSelectors[0] }); break; } case nameof(QueryableExtensions.OrderByThenBysHelper): { var resultType = result.NewSelectors.Count > 0 ? result.NewSelectors[0].ReturnType : null; var newParameterType = result.NewSource.Type.GetSequenceElementType(); var method = methodCallExpression.Method.GetGenericMethodDefinition(); var methodWithElementSelector = method.GetGenericArguments().Length == 1 ? method.MakeGenericMethod(newParameterType) : method.MakeGenericMethod(newParameterType, resultType); var newSource = result.NewSource; newCall = Expression.Call ( null, methodWithElementSelector, new[] { newSource, Expression.Constant(result.NewSelectors.ToArray()), methodCallExpression.Arguments[2] } ); break; } case "GroupBy": { var keyType = result.NewSelectors[0].ReturnType; var newParameterType = result.NewSelectors[0].Parameters[0].Type; var elementType = methodCallExpression.Method.ReturnType.GetGenericArguments()[0].GetGenericArguments()[1]; var methodWithElementSelector = MethodInfoFastRef .QueryableGroupByWithElementSelectorMethod .MakeGenericMethod(newParameterType, keyType, elementType); if (methodCallExpression.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.QueryableGroupByMethod) { if (result.Changed) { var elementSelectorParameter = Expression.Parameter(newParameterType); var elementSelectorBody = CreateExpressionForPath(result.ReferencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, result.IndexByPath); var elementSelector = Expression.Lambda(elementSelectorBody, elementSelectorParameter); newCall = Expression.Call(methodWithElementSelector, result.NewSource, result.NewSelectors[0], elementSelector); } else { newCall = Expression.Call(methodCallExpression.Method, result.NewSource, result.NewSelectors[0]); } } else if (methodCallExpression.Method.GetGenericMethodOrRegular() == MethodInfoFastRef.QueryableGroupByWithElementSelectorMethod) { if (result.Changed) { var existingElementSelector = methodCallExpression.Arguments[2].StripQuotes(); var elementSelectorParameter = Expression.Parameter(newParameterType); var pathExpression = CreateExpressionForPath(result.ReferencedObjectPaths.Count, PropertyPath.Empty, elementSelectorParameter, result.IndexByPath); var newBody = SqlExpressionReplacer.Replace(existingElementSelector.Body, existingElementSelector.Parameters[0], pathExpression); var elementSelector = Expression.Lambda(newBody, elementSelectorParameter); newCall = Expression.Call(methodWithElementSelector, result.NewSource, result.NewSelectors[0], elementSelector); } else { newCall = Expression.Call(methodCallExpression.Method, result.NewSource, result.NewSelectors[0], result.NewSelectors[1]); } } else { throw new NotSupportedException($"Unsupport method when using explicit joins: {methodCallExpression.Method}"); } break; } default: { var resultType = result.NewSelectors.Count > 0 ? result.NewSelectors[0].ReturnType : null; var newParameterType = result.NewSource.Type.GetSequenceElementType(); var method = methodCallExpression.Method.GetGenericMethodDefinition(); var methodWithElementSelector = method.GetGenericArguments().Length == 1 ? method.MakeGenericMethod(newParameterType) : method.MakeGenericMethod(newParameterType, resultType); newCall = Expression.Call ( null, methodWithElementSelector, result.NewSelectors.Count > 0 ? new[] { result.NewSource, result.NewSelectors[0] } : new[] { result.NewSource } ); break; } } this.replacementExpressionForPropertyPathsByJoin.Add(new Pair <Expression, Dictionary <PropertyPath, Expression> >(newCall, result.ReplacementExpressionsByPropertyPath)); return(Reselect(newCall, result.ReferencedObjectPaths, result.IndexByPath)); }
private MethodCallExpression MakeJoinCallExpression(int index, Expression left, Expression right, PropertyPath targetPath, Dictionary <PropertyPath, int> indexByPath, Dictionary <PropertyPath, Expression> rootExpressionsByPath, Expression sourceParameterExpression) { Expression leftObject; var leftElementType = left.Type.GetGenericArguments()[0]; var rightElementType = right.Type.GetGenericArguments()[0]; var rootPath = targetPath.RemoveLast(); var leftSelectorParameter = Expression.Parameter(leftElementType); if (index == 1 && rootExpressionsByPath.ContainsKey(rootPath)) { leftObject = rootExpressionsByPath[rootPath]; leftObject = SqlExpressionReplacer.Replace(leftObject, c => c == sourceParameterExpression ? leftSelectorParameter : null); } else { leftObject = CreateExpressionForPath(index - 1, rootPath, leftSelectorParameter, indexByPath); if (rootExpressionsByPath.ContainsKey(rootPath)) { foreach (var property in rootPath) { leftObject = Expression.Property(leftObject, property.Name); } } } LambdaExpression leftSelector, rightSelector; var projector = MakeJoinProjector(leftElementType, rightElementType); if (targetPath.Last.GetMemberReturnType().GetGenericTypeDefinitionOrNull() == typeof(RelatedDataAccessObjects <>)) { leftSelector = Expression.Lambda(leftObject, leftSelectorParameter); var typeDescriptor = this.model.TypeDescriptorProvider.GetTypeDescriptor(leftObject.Type); var relationship = typeDescriptor .GetRelationshipInfos() .Where(c => c.RelationshipType == RelationshipType.ParentOfOneToMany) .Single(c => c.ReferencingProperty.PropertyName == targetPath.Last.Name); var rightSelectorParameter = Expression.Parameter(rightElementType); rightSelector = Expression.Lambda(Expression.Property(rightSelectorParameter, relationship.TargetProperty), rightSelectorParameter); } else { leftSelector = Expression.Lambda(Expression.Property(leftObject, targetPath.Last().Name), leftSelectorParameter); var rightSelectorParameter = Expression.Parameter(rightElementType); rightSelector = Expression.Lambda(rightSelectorParameter, rightSelectorParameter); } var method = MethodInfoFastRef .QueryableExtensionsLeftJoinMethod .MakeGenericMethod(leftElementType, rightElementType, leftSelector.ReturnType, projector.ReturnType); var retval = Expression.Call(method, left, right, Expression.Quote(leftSelector), Expression.Quote(rightSelector), Expression.Quote(projector)); return(retval); }