【问题标题】:C# LINQ - selecting a dynamic object based on properties defined at runtimeC# LINQ - 根据运行时定义的属性选择动态对象
【发布时间】:2019-02-13 02:22:46
【问题描述】:

我一直在尝试创建一个表达式,它可以将强类型的 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 切换出 dyanmic / ExpandoObject,它工作得很好,但会返回 Message 类的一个实例,其所有属性都为其默认值。

以前有人做过吗?

干杯。

【问题讨论】:

  • 为什么不直接转换为动态,为什么要为此创建表达式?
  • 因为直到运行时我才知道选择哪些字段
  • 基本上你想动态过滤你的属性数据集
  • 你为什么不把所有的结果都拉到一个key to value的字典中,很确定json序列化器会默认创建一个对象

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


【解决方案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);
}

【讨论】:

  • DynamicClassFactory 是否创建 AnonymousType?
  • @johnny5 不。匿名类型是 C# 编译时特性。它正在创建运行时类型(带有必要的缓存)。链接中对此进行了解释 - 数据类是在当前应用程序域的内存程序集中创建的。。它生成的运行时类的名称类似于 C# 生成的匿名类型:)
  • 感谢您的澄清
【解决方案2】:

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

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

安装 FastMember

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

Install-Package FastMember -Version 1.4.1

写函数

对于任何泛型类型,我们的函数/方法将是 Extension Method,例如 TEntity,它必须是 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&lt;string,object&gt;。因此,这意味着我们可以扩展它并在运行时为其添加动态属性,从而使我们更容易。

测试用例时间

使用NUnit 对上述方法运行测试用例大约需要 40 毫秒,这听起来已经足够了。

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

干杯!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-18
    • 1970-01-01
    • 2015-11-22
    • 2017-04-21
    • 1970-01-01
    相关资源
    最近更新 更多