EF Linq-动态Lamdba表达式树

时间:2019-05-30 02:16:49

标签: c# entity-framework linq lambda

我有一个通用存储库,该存储库使用通用表达式从Entity Framework Core返回数据。

public async Task<T2> GetFieldsAsync<T2>(Expression<Func<T, T2>> expression)
  {
            return await context.Set<T>()
                                .Select(expression)
                                .FirstOrDefaultAsync();
  }

现在,如果要选择特定字段,可以在编译时编写以下语句:

var test = await _repositoryBase.GetFieldsAsync(x => new { x.Id, x.Name });

我希望能够在运行时执行以上操作。我可以在运行时创建一个表达式,该表达式返回单个参数,如下所示:

var expression = Expression.Parameter(typeof(Ingredient), "ingr");
var param1 = Expression.Property(expression, "Id");
var lambda = Expression.Lambda<Func<Ingredient, Guid>>(param1, expression);
var test = await _repositoryBase.GetFieldsAsync(lambda);

上面的lambda仅返回Ingredient类的单个属性。 是否可以创建使用表达式树返回匿名对象的运行时lambda?

x => new { x.Id, x.Name }

请注意,用户可能会请求其他字段(例如,名称,说明,DateCreated等),因此需要动态创建lambda表达式。

我知道我可以使用https://github.com/StefH/System.Linq.Dynamic.Core通过其内置的IQueryable扩展方法传入字符串以选择语句。我想知道是否存在一种通过用户传递的字段列表在运行时动态选择特定字段的方法。


当前,我的方法是从数据库中获取类的所有属性,并使用ExpandoObject仅选择用户请求的字段。这里的问题是,尽管它可以工作,但我从数据库返回的数据超出了要求。我只想选择所需的数据来避免这种情况。

//user did not provide any fields so include all fields
    if (string.IsNullOrEmpty(field))
     {
         myPropertyInfoList.AddRange(typeProperties);
     }
    else
      {
        foreach (var item in fields)
        {
            myPropertyInfoList.Add(type.GetProperty(item, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance));
        }

    var expandoObj = (IDictionary<string, object>)new ExpandoObject();

        foreach (var item in myPropertyInfoList)
        {
            expandoObj.Add(item.Name, item.GetValue(ingrView));
        }

2 个答案:

答案 0 :(得分:0)

这是一个简化的匿名类型创建者。它使用公共字段而不是构建属性,它没有实现任何方法(它具有默认的构造函数)。

首先,我稍后使用一种简单的扩展方法:

public static class StringExt {
    public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings);    
}

在这里,该代码用于根据描述匿名类型的Dictionary<string,Type>创建新的匿名类型。生成具有用于每个字段(属性)类型的类型参数的泛型类型,然后将其固定为所使用的实际类型-这是C#编译器生成匿名类型的方式。此代码创建(一次)动态程序集和模块,然后根据需要添加新类型。它使用缓存来(尝试)防止多次创建同一类型。

public static class AnonymousExt {
    private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
    private static ModuleBuilder AnonTypeMB;
    private static int AssemCount = 25;

    // create a pseudo anonymous type (POCO) from an IDictionary of property names and values
    // using public fields instead of properties
    // no methods are defined on the type
    public static Type MakeAnonymousType(IDictionary<string, Type> objDict) {
        // find or create AssemblyBuilder for dynamic assembly
        if (AnonTypeMB == null) {
            var assemblyName = new AssemblyName($"MyDynamicAssembly{AssemCount}");
            var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            AnonTypeMB = ab.DefineDynamicModule("MyDynamicModule");
        }
        // get a dynamic TypeBuilder
        var typeBuilder = AnonTypeMB.DefineType($"<>f__AnonymousType{AssemCount++}`{objDict.Keys.Count}", TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
        typeBuilder.SetCustomAttribute(CompilerGeneratedAttributeBuilder);

        // create generic parameters for every field
        string gtpName(string fieldName) => $"<{fieldName}>j__TPar";
        var gtpnames = objDict.Keys.Select(k => gtpName(k)).ToArray();
        var gtpbs = typeBuilder.DefineGenericParameters(gtpnames);
        var gtpN2Bs = gtpnames.Zip(gtpbs, (n, pb) => new { n, pb }).ToDictionary(g => g.n, g => g.pb);

        // add public fields to match the source object
        var fbs = new List<FieldBuilder>();
        foreach (var srcFieldName in objDict.Keys)
            fbs.Add(typeBuilder.DefineField(srcFieldName, gtpN2Bs[gtpName(srcFieldName)], FieldAttributes.Public));

        // Fix the generic class
        var fieldTypes = objDict.Values.ToArray();        
        return typeBuilder.CreateType().MakeGenericType(fieldTypes);
    }

    static string MakeAnonymousTypeKey(IDictionary<string, Type> objDict) => objDict.Select(d => $"{d.Key}~{d.Value}").Join("|");

    public static Dictionary<string, Type> PrevAnonTypes = new Dictionary<string, Type>();
    public static Type FindOrMakeAnonymousType(IDictionary<string, Type> objDict) {
        var wantedKey = MakeAnonymousTypeKey(objDict);
        if (!PrevAnonTypes.TryGetValue(wantedKey, out var newType)) {
            newType = MakeAnonymousType(objDict);
            PrevAnonTypes[wantedKey] = newType;
        }

        return newType;
    }    
}

这是一些示例代码,将其与名为Accounts的SQL表一起使用,该表的类类型为Accounts。因为我的匿名类型没有采用字段值的构造函数,所以我使用了MemberInitExpression而不是普通的NewExpression(无论如何看起来都不必要复杂?)。

var objDescr = new Dictionary<string, Type> { { "Actid", typeof(Int32) }, { "Application", typeof(string) }, { "Username", typeof(string) }};
var aType = AnonymousExt.FindOrMakeAnonymousType(objDescr);

var parma = Expression.Parameter(typeof(Accounts), "a");
var fxbe = aType.GetFields().Select(fi => Expression.Bind(fi, Expression.Field(parma, fi.Name))).ToArray();
var mie = Expression.MemberInit(Expression.New(aType), fxbe);
var myf = Expression.Lambda<Func<Accounts, object>>(mie, parma);

var ans = Accounts.Select(myf).Take(2);

答案 1 :(得分:0)

匿名类型只是C#在编译时为您构建的类型。与其尝试构建匿名类型,不如为生成的查询的每一行返回一个object[]。另外,这降低了处理返回数据的复杂性。

List<string> properties = ... ;
var parameter = Expression.Parameter(typeof(T), "e");

var selectExpression = Expression.Lambda<Func<T, object[]>>(
    Expression.NewArrayInit(
        typeof(object),
        properties.Select(p =>
        {
            var ret = Expression.Property(parameter, p);
            if (ret.Type != typeof(object))
                ret = Expression.Convert(ret, typeof(object));
            return ret;
        })
    ),
    parameter);