/// <summary> /// Projects query to given generic argument type. /// For example: Converting Entity queries to DTO queries. /// </summary> /// <typeparam name="TOut">Target type.</typeparam> /// <param name="query">The query.</param> /// <param name="mapDefinitions">Map definitions (property mappings) for source and destination types.</param> /// <returns>Projected query.</returns> public static IQueryable <TOut> ProjectTo <TOut>(this IQueryable query, IMapDefinitionCollection mapDefinitions = null) { var projector = CreateProjector(query.ElementType, typeof(TOut), GetIncludes(query), mapDefinitions); return((IQueryable <TOut>)query.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Select", new Type[] { query.ElementType, typeof(TOut) }, query.Expression, projector) )); }
/// <summary> /// Generates projector lambda for given generic argument types. /// </summary> /// <typeparam name="TIn">Source type.</typeparam> /// <typeparam name="TOut">Target type.</typeparam> /// <param name="includes">Navigation properties to include in projection.</param> /// <param name="mapDefinitions">Map definitions (property mappings) for source and destination types.</param> /// <returns>The reusable lambda expression.</returns> public static Expression <Func <TIn, TOut> > CreateProjector <TIn, TOut>(IEnumerable <string> includes = null, IMapDefinitionCollection mapDefinitions = null) { return((Expression <Func <TIn, TOut> >)CreateProjector(typeof(TIn), typeof(TOut), includes, mapDefinitions)); }
/// <summary> /// Creates projector object initializer for given types. /// </summary> /// <param name="inType">Source type.</param> /// <param name="outType">Target type.</param> /// <param name="includes">Navigation properties to include in projection.</param> /// <param name="prmExp">ParameterExpression instance to point the source object.</param> /// <returns>The reusable projector object initializer.</returns> /// <param name="mapDefinitions">Map definitions (property mappings) for source and destination types.</param> private static MemberInitExpression CreateAssigner(Type inType, Type outType, IEnumerable <string> includes, IMapDefinitionCollection mapDefinitions, Expression prmExp) { var inProperties = GetMapFields(inType); var outProperties = GetMapFields(outType, true); IMapDefinition mapDefinition; if (mapDefinitions != null) { mapDefinition = mapDefinitions.Resolve(inType, outType); } else { mapDefinition = null; } Dictionary <string, Tuple <MapMember, MapMember> > navigationMapping = new Dictionary <string, Tuple <MapMember, MapMember> >(); var memberBindings = new List <MemberBinding>(); foreach (var outProperty in outProperties) { string mappedInPropertyName; if (mapDefinition != null) { if (!mapDefinition.Maps.TryGetValue(outProperty.Name, out mappedInPropertyName)) { if (mapDefinition.OnlyExplicit) { continue; } mappedInPropertyName = outProperty.Name; } } else { mappedInPropertyName = outProperty.Name; } var inProperty = inProperties.FirstOrDefault(p => p.Name == mappedInPropertyName); if (inProperty != null) { if (inProperty.IsPrimitive) { var p1Exp = Expression.PropertyOrField(prmExp, inProperty.Name); memberBindings.Add(Expression.Bind(outProperty.MemberInfo, p1Exp)); } else { navigationMapping[inProperty.Name] = new Tuple <MapMember, MapMember>(inProperty, outProperty); } } } if (includes != null && includes.Any()) { var includeGroups = includes .Select(i => { var idx = i.IndexOf("."); string path, other; if (idx > 0) { path = i.Substring(0, idx); other = i.Substring(idx + 1); } else { path = i; other = null; } return(new { path, other }); }) .GroupBy(i => i.path) .Select(i => new { Property = i.Key, SubIncludes = i.Select(o => o.other).Where(o => o != null) }) .ToList(); foreach (var includeGroup in includeGroups) { Tuple <MapMember, MapMember> includeMapping; if (!navigationMapping.TryGetValue(includeGroup.Property, out includeMapping)) { continue; } var inIncludeProperty = includeMapping.Item1; var outIncludeProperty = includeMapping.Item2; var inIncludeType = inIncludeProperty.Type; var outIncludeType = outIncludeProperty.Type; var inIncludeEnumerableType = inIncludeType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>)); var outIncludeEnumerableType = outIncludeType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>)); if (inIncludeEnumerableType != null) { if (outIncludeEnumerableType == null) { throw new ArrayTypeMismatchException($"Navigation type mismatch for property {includeGroup.Property}"); } inIncludeType = inIncludeEnumerableType.GetGenericArguments()[0]; outIncludeType = outIncludeEnumerableType.GetGenericArguments()[0]; } else if (outIncludeEnumerableType != null) { throw new ArrayTypeMismatchException($"Navigation type mismatch for property {includeGroup.Property}"); } Expression subProjector; if (inIncludeEnumerableType != null) { subProjector = CreateProjector(inIncludeType, outIncludeType, includeGroup.SubIncludes, mapDefinitions); var subPropExp = Expression.PropertyOrField(prmExp, includeGroup.Property); subProjector = Expression.Call(typeof(Enumerable), "Select", new Type[] { inIncludeType, outIncludeType }, subPropExp, subProjector); subProjector = Expression.Call(typeof(Enumerable), "ToList", new Type[] { outIncludeType }, subProjector); } else { var subPrmExp = Expression.MakeMemberAccess(prmExp, inIncludeProperty.MemberInfo); subProjector = CreateAssigner(inIncludeType, outIncludeType, includeGroup.SubIncludes, mapDefinitions, subPrmExp); } memberBindings.Add(Expression.Bind(outIncludeProperty.MemberInfo, subProjector)); } } return(Expression.MemberInit(Expression.New(outType), memberBindings)); }
/// <summary> /// Generates projector lambda expression for given types. /// </summary> /// <param name="inType">Source type.</param> /// <param name="outType">Target type.</param> /// <param name="includes">Navigation properties to include in projection.</param> /// <returns>The reusable projector lambda expression.</returns> /// <param name="mapDefinitions">Map definitions (property mappings) for source and destination types.</param> public static LambdaExpression CreateProjector(Type inType, Type outType, IEnumerable <string> includes = null, IMapDefinitionCollection mapDefinitions = null) { var prmExp = Expression.Parameter(inType, "e"); var memberInit = CreateAssigner(inType, outType, includes, mapDefinitions, prmExp); return(Expression.Lambda(memberInit, prmExp)); }