C # LINQ - выбор динамического объекта на основе свойств, определенных во время выполнения - PullRequest
0 голосов
/ 07 сентября 2018

Я пытался создать выражение, которое может проецировать строго типизированный объект EF Core в динамический объект, содержащий список, который определяется во время выполнения с помощью вызова REST API.

Это то, что я имею до сих пор:

Expression<Func<Message, dynamic>> DynamicFields(IEnumerable<string> fields)
{
    var xParameter = Expression.Parameter(typeof(Message), "o");
    var xNew = Expression.New(typeof(ExpandoObject));

    var bindings = fields.Select(o => {
        var mi = typeof(Message).GetProperty(o);
        var xOriginal = Expression.Property(xParameter, mi);
        return Expression.Bind(mi, xOriginal);
    });

    var xInit = Expression.MemberInit((dynamic)xNew, bindings);

    return Expression.Lambda<Func<Message, dynamic>>(xInit, xParameter);
}

Такое ощущение, что я очень близок, но это взрывает во время выполнения, заявляя, что свойство X не является членом ExpandoObject. Я попытался изменить использование динамического и ExpandoObject, но, похоже, ничего не работает - это вообще возможно?

Если я отключаю dyanmic / ExpandoObject для Message, он работает просто отлично, но возвращает экземпляр класса Message со всеми его свойствами со значениями по умолчанию.

Кто-нибудь делал это раньше?

Приветствие.

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

У меня есть идея, как это сделать без использования Expressions. По-видимому, поскольку Reflection в c # работает медленно, как объясняется далее в этой статье , я решил использовать библиотеку с именем FastMember , чтобы получить свойства объекта.

==============

Установить FastMember

Выполните следующую команду в PackageManagerConsole, чтобы установить библиотеку.

Install-Package FastMember -Version 1.4.1

Функция записи

Наша функция / метод будет Метод расширения для любого универсального типа, например TEntity, который должен быть class и принимает массив полей в качестве параметра для сообщить функции, какие свойства TEntity возвращать.

using FastMember;
using Dynamic;
using System;
using System.Linq;
using System.Collections.Generic

public static class Utilities
{
    public static dynamic GetDynamicFields<TEntity>(this TEntity entity, params string[] fields)
        where TEntity : class
    {
        dynamic dynamic = new ExpandoObject();
        // ExpandoObject supports IDictionary so we can extend it like this
        IDictionary<string,object> dictionary = dynamic as IDictionary<string,object>;

        TypeAccessor typeAccessor = TypeAccessor.Create(typeof(TEntity));
        ObjectAccessor accessor = ObjectAccessor.Create(entity);
        IDictionary<string,Member> members = typeAccessor.GetMembers().ToDictionary(x => x.Name);

        for (int i = 0; i < fields.Length; i++)
        {
            if (members.ContainsKey(fields[i]))
            {
                var prop = members[fields[i]];

                if (dictionary.ContainsKey(prop.Name))
                    dictionary[prop.Name] = accessor[prop.Name];
                else
                    dictionary.Add(prop.Name, accessor[prop.Name]);
            }
        }

        return dynamic;
    }
}

Как использовать

Использовать метод просто и можно сделать так:

Message msg = new Message();
dynamic result = msg.GetDynamicFields("Id","Text");

В c # dynamic является примером ExpandoObject. Посмотрев на код для ExpandoObject, я понял, что он реализует IDictionary<string,object>. Следовательно, это означает, что мы можем расширить его и добавить к нему динамические свойства во время выполнения, что облегчит нам задачу.

TestCase Time

Выполнение тестового примера по вышеуказанному методу с использованием NUnit , занимает примерно 40 мс , что звучит достаточно хорошо.

Надеюсь, что это решение подойдет вам или, возможно, даст вам идеи о том, как решить вашу проблему.

Ура!

0 голосов
/ 07 сентября 2018

Невозможно напрямую проецировать на ExpandoObject / dynamic.

Но вы можете проецировать на динамически создаваемые типы во время выполнения.Например, Microsoft.EntityFrameworkCore.DynamicLinq пакет - Динамические классы данных .

Если вы установите пакет , сформируйте nuget (вы можете разработать аналогфункциональность самостоятельно, но вы должны в основном реализовать все, что делает часть пакета), рассматриваемый метод может быть реализован следующим образом:

using System.Linq.Dynamic.Core;

static Expression<Func<TSource, dynamic>> DynamicFields<TSource>(IEnumerable<string> fields)
{
    var source = Expression.Parameter(typeof(TSource), "o");
    var properties = fields
        .Select(f => typeof(TSource).GetProperty(f))
        .Select(p => new DynamicProperty(p.Name, p.PropertyType))
        .ToList();
    var resultType = DynamicClassFactory.CreateType(properties, false);
    var bindings = properties.Select(p => Expression.Bind(resultType.GetProperty(p.Name), Expression.Property(source, p.Name)));
    var result = Expression.MemberInit(Expression.New(resultType), bindings);
    return Expression.Lambda<Func<TSource, dynamic>>(result, source);
}
...