C#LINQ-根据运行时定义的属性选择动态对象

时间:2018-09-07 17:55:10

标签: c# linq dynamic entity-framework-core expression-trees

我一直在尝试创建一个表达式,该表达式可以将强类型的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 :(得分:1)

无法直接投影到ExpandoObject / dynamic

但是您可以在运行时投影为动态创建的类型。例如,参见Microsoft.EntityFrameworkCore.DynamicLinq软件包- Dynamic Data Classes

如果您安装了package形式的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);
}

答案 1 :(得分:0)

我对不使用Expressions的情况有一个想法。显然,由于c#中的Reflection较慢,如本Article所述,我决定使用名为FastMember的库来获取对象属性。

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

安装FastMember

在PackageManagerConsole中运行以下命令以安装该库。

Install-Package FastMember -Version 1.4.1

写入功能

对于任何泛型类型,例如TEntity,我们的函数/方法将是Extension Method,必须是class并采用 fields 数组参数,告诉函数要返回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#中,dynamicExpandoObject的示例。看了ExpandoObject的代码后,我意识到它实现了IDictionary<string,object>。因此,这意味着我们可以在运行时扩展它并为其添加动态属性,从而使我们更容易。

测试案例时间

使用NUnit在上述方法上运行测试用例大约需要 40ms ,这听起来足够好。

希望此解决方案对您有用,或者可能为您提供解决问题的方法。

干杯!