EF Linq-动态Lambda表达式树 [英] EF Linq- Dynamic Lambda expression trees

查看:100
本文介绍了EF Linq-动态Lambda表达式树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a generic repository that uses generic expressions for returning data from Entity Framework Core.

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

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

Now if I want to select specific fields, at compile time I can write the following statement:

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

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

I want to be able to do above at run time instead. I can create an expression at run time that returns a single parameter as follows:

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仅返回Ingredients类中的单个属性. 是否可以创建一个使用表达式树返回匿名对象的运行时lambda?

The above lambda only returns a single property from the Ingredient class. Is it possible to create a run time lambda that returns an anonymous object using expression trees? I.e.

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

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

Note that users might request different fields (e.g. Name, Description, DateCreated, etc.) so need to dynamically create the lambda expression.

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

I know I can use https://github.com/StefH/System.Linq.Dynamic.Core to pass in strings to select statements via its built-in IQueryable extension methods. I am wondering if there is a way to dynamically select specific fields at run time via a list of fields passed in by the user.

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

Currently, my approach is to get all properties of the class from the database, and use an ExpandoObject to only select the fields that user requests. The issue here is that although it works, I am returning more data from the database than is required. I'd like to avoid this by only selecting the required data.

//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));
        }

推荐答案

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

This is a simplified anonymous type creator. It uses public fields instead of building properties, it has no methods implemented (it gets a default constructor).

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

First, a simple extension method I use later:

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

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

Here, the code to create a new anonymous type from a Dictionary<string,Type> that describes the anonymous type. A generic type with type parameters for the type of every field (property) is generated and then fixed to the actual types being used - which is how the C# compiler generates anonymous types. This code creates (once) a dynamic Assembly and Module, then adds new types as needed. It uses a cache to (attempt) to prevent creating the same type more than once.

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;
    }    
}

这是一些示例代码,将其与SQL表Accounts一起使用,该表的类类型为Accounts.因为我的匿名类型没有采用字段值的构造函数,所以我使用了MemberInitExpression而不是普通的NewExpression(反正这似乎不必要地复杂吗?).

And here is some sample code using it with a SQL Table named Accounts which has a class type named Accounts. Because my anonymous type doesn't have a constructor that takes the field values, I use a MemberInitExpression instead of the normal NewExpression (which seems unnecessarily complicated anyway?).

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);

这篇关于EF Linq-动态Lambda表达式树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆