To pick an anonymous type, establish a LINQ Expression Tree.

c# entity-framework expression-trees linq linq-to-entities

Question

I'd want to use expression trees to dynamically build the following choose statement:

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

I've figured out how to create

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

I can provide many properties in my choose function, but I can't seem to locate a constructor or overload that would enable me do so.

1
45
3/5/2015 8:59:08 PM

Accepted Answer

As previously noted, Reflection Emit and the helper class I've provided below may be used to do this. Take what you will from the code below—"it works on my box"—it is a work in progress. A static extension method class should be included with the SelectDynamic method class.

Since the type isn't generated until runtime, as intended, you won't get any Intellisense. works well with controls for late-bound data.

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));
    }
}
70
10/31/2016 12:33:14 PM

Popular Answer

Although the accepted response is really helpful, I was looking for something a bit more authentically anonymous.

A true anonymous type contains properties that can only be read, a constructor for entering all of the values, an implementation of Equals/GetHashCode for comparing the values of each property, and an implementation of ToString that includes the name and value of each property. (For a further explanation of anonymous types, see https://msdn.microsoft.com/en-us/library/bb397696.aspx.)

I created a class called https://github.com/dotlattice/LatticeUtils/blob/master/LatticeUtils/AnonymousTypeUtils.cs on github that creates dynamic anonymous types based on that definition of anonymous classes. In order to ensure that the fake anonymous types behave like actual ones, the project also includes some unit tests.

Here is a very simple example of how to use it:

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

Another point: I discovered that the constructor has to be called with the "members" argument specified when using a dynamic anonymous type with Entity Framework. For instance:

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

Entity Framework wouldn't identify Expression.New as the constructor of an anonymous type if you used one of the variants that does not include the "members" argument. So, based on that, I infer that the constructor statement of a genuine anonymous type would also include the "members" data.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow