How to create LINQ Expression Tree to select an anonymous type

前端 未结 9 663
夕颜
夕颜 2020-11-22 12:12

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

var v = from c in Countries
        where c.City == \"London\"
           


        
相关标签:
9条回答
  • 2020-11-22 12:53

    You could use the IQueryable-Extensions here, which is an implemantation of the solution described by "Ethan J. Brown":

    https://github.com/thiscode/DynamicSelectExtensions

    The Extension builds dynamically an anonymous type.

    Then you can do this:

    var YourDynamicListOfFields = new List<string>(
    
        "field1",
        "field2",
        [...]
    
    )
    var query = query.SelectPartially(YourDynamicListOfFields);
    
    0 讨论(0)
  • 2020-11-22 12:57

    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));
        }
    }
    
    0 讨论(0)
  • 2020-11-22 12:57

    You could use a parameter class instead of working with an anonymous type. In your example you can create a parameter class like this:

    public struct ParamClass {
        public string Name { get; set; };
        public int Population { get; set; };
    }
    

    …and put it into your select like this:

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

    What you get out is something of the type IQueryable<ParamClass>.

    0 讨论(0)
提交回复
热议问题