我一直在尝试创建一个表达式,它可以将强类型的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类的一个实例,其所有属性都是默认值。
有人这么做过吗?
干杯。
无法直接投射到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);
}
我不知道如何在不使用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#中,
dynamic
是ExpandoObject
的一个例子。在查看ExpandoObject
的代码后,我意识到它实现了IDictionary<string,object>
。因此,这意味着我们可以在运行时扩展它并为其添加动态属性,从而使我们更容易。
TestCase时间
使用NUnit在上述方法上运行测试用例大约需要40ms,这听起来不错。
希望这个解决方案适合您,或者为您提供有关如何解决问题的想法。
干杯!