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

问题描述 投票:1回答:2

我一直在尝试创建一个表达式,它可以将强类型的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的使用,但似乎没有任何工作 - 这甚至可能吗?

如果我为Message切换了dynamic / ExpandoObject,它可以正常工作,但返回Message类的一个实例,其所有属性都是默认值。

有人这么做过吗?

干杯。

c# linq dynamic entity-framework-core expression-trees
2个回答
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);
}

0
投票

我不知道如何在不使用Expressions的情况下解决这个问题。显然,由于c#中的Reflection在这个Article中进一步解释很慢,我决定使用一个名为FastMember的库来获取对象属性。

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

安装FastMember

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

Install-Package FastMember -Version 1.4.1

写功能

我们的函数/方法将成为任何泛型类型的Extension Method,例如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#中,dynamicExpandoObject的一个例子。在查看ExpandoObject的代码后,我意识到它实现了IDictionary<string,object>。因此,这意味着我们可以在运行时扩展它并为其添加动态属性,从而使我们更容易。

TestCase时间

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

希望这个解决方案适合您,或者为您提供有关如何解决问题的想法。

干杯!

© www.soinside.com 2019 - 2024. All rights reserved.