Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create LINQ Expression Tree to select an anonymous type

I would like to generate the following select statement dynamically using expression trees:

var v = from c in Countries         where c.City == "London"         select new {c.Name, c.Population}; 

I have worked out how to generate

var v = from c in Countries         where c.City == "London"         select new {c.Name}; 

but I cannot seem to find a constructor/overload that will let me specify multiple properties in my select lambda.

like image 835
Tom Deloford Avatar asked Mar 03 '09 11:03

Tom Deloford


2 Answers

This can be done, as mentioned, with the help of Reflection Emit and a helper class I've included below. The code below is a work in progress, so take it for what it's worth... 'it works on my box'. The SelectDynamic method class should be tossed in a static extension method class.

As expected, you won't get any Intellisense since the type isn't created until runtime. Works good on late-bound data controls.

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames) {     Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));     Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);      ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");     IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();      Expression selector = Expression.Lambda(Expression.MemberInit(         Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);      return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },                  Expression.Constant(source), selector)); }    public static class LinqRuntimeTypeBuilder {     private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);     private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };     private static ModuleBuilder moduleBuilder = null;     private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();      static LinqRuntimeTypeBuilder()     {         moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);     }      private static string GetTypeKey(Dictionary<string, Type> fields)     {         //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter         string key = string.Empty;         foreach (var field in fields)             key += field.Key + ";" + field.Value.Name + ";";          return key;     }      public static Type GetDynamicType(Dictionary<string, Type> fields)     {         if (null == fields)             throw new ArgumentNullException("fields");         if (0 == fields.Count)             throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");          try         {             Monitor.Enter(builtTypes);             string className = GetTypeKey(fields);              if (builtTypes.ContainsKey(className))                 return builtTypes[className];              TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);              foreach (var field in fields)                                     typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);              builtTypes[className] = typeBuilder.CreateType();              return builtTypes[className];         }         catch (Exception ex)         {             log.Error(ex);         }         finally         {             Monitor.Exit(builtTypes);         }          return null;     }       private static string GetTypeKey(IEnumerable<PropertyInfo> fields)     {         return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));     }      public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)     {         return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));     } } 
like image 62
Ethan J. Brown Avatar answered Sep 27 '22 21:09

Ethan J. Brown


The accepted answer is very useful, but I needed something a little closer to a real anonymous type.

A real anonymous type has read-only properties, a constructor for filling in all of the values, an implementation of Equals/GetHashCode for comparing the values of each property, and an implementation ToString that includes the name/value of each property. (See https://msdn.microsoft.com/en-us/library/bb397696.aspx for a full description of anonymous types.)

Based on that definition of anonymous classes, I put a class that generates dynamic anonymous types on github at https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs. The project also contains some unit tests to make sure the fake anonymous types behave like real ones.

Here's a very basic example of how to use it:

AnonymousTypeUtils.CreateObject(new Dictionary<string, object> {     { "a", 1 },     { "b", 2 } }); 

Also, another note: I found that when using a dynamic anonymous type with Entity Framework, the constructor must be called with the "members" parameter set. For example:

Expression.New(     constructor: anonymousType.GetConstructors().Single(),      arguments: propertyExpressions,     members: anonymousType.GetProperties().Cast<MemberInfo>().ToArray() );  

If you used one of the versions of Expression.New that does not include the "members" parameter, Entity Framework would not recognize it as the constructor of an anonymous type. So I assume that means a real anonymous type's constructor expression would include that "members" information.

like image 29
dotlattice Avatar answered Sep 27 '22 22:09

dotlattice