Э. Ф. Линк - Деревья динамических выражений Ламдбы - PullRequest
0 голосов
/ 30 мая 2019

У меня есть общий репозиторий, который использует универсальные выражения для возврата данных из 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);

Вышеупомянутая лямбда-функция возвращает только одно свойство из класса Ingredient. Можно ли создать лямбду времени выполнения, которая возвращает анонимный объект, используя деревья выражений? Т.е.

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

Обратите внимание, что пользователи могут запрашивать различные поля (например, Имя, Описание, DateCreated и т. Д.).) поэтому нужно динамически создавать лямбда-выражения.

Я знаю, что могу использовать 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));
        }

1 Ответ

0 голосов
/ 01 июня 2019

Это упрощенный создатель анонимного типа.Он использует открытые поля вместо свойств здания, у него нет реализованных методов (он получает конструктор по умолчанию).

Во-первых, простой метод расширения, который я использую позже:

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

А вот пример кода, использующего его с таблицей SQL с именем Accounts, который имеет тип класса с именем 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...