我有一個簡單的運算式,用于將域物件轉換為 DTO。
public static Expression<Func<Person, PersonDetailsShallow>> ToPersonDetailsShallow
=> (person) => new PersonDetailsShallow()
{
PersonId = person.Id,
Tag = person.Tag
};
public class Person
{
public string Id { get; internal set; }
public string Tag { get; internal set; }
}
public class PersonDetailsShallow
{
public string PersonId { get; internal set; }
public string Tag { get; internal set; }
}
我現在正在夢想一種將其嵌入Expression到另一個運算式中的方法,例如
// define one more entity and dto to have something to work with
public class Purchase
{
public double Price { get; internal set; }
public Person Purchaser { get; internal set; }
}
public class PurchaseDetailsShallow
{
public double Price { get; internal set; }
public PersonDetailsShallow Purchaser { get; internal set; }
}
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ExpandExpression(ToPersonDetailsShallow, purchase.Purchaser)
}
whereExpandExpression有一些魔力,結果ToPurchaseDetailsShallow看起來像這樣
(purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = new PersonDetailsShallow()
{
PersonId = purchase.Purchaser.Id,
Tag = purchase.Purchaser.Tag
}
}
似乎有一些庫可以實作這一點,如this question所示
我可以重用代碼來為 EF Core 的子屬性選擇自定義 DTO 物件嗎?
但我希望有一種更簡單的方法,不涉及添加新的依賴項。
我知道我可以用Compile()la偽造它
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> (purchase) => new PurchaseDetailsShallow()
{
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Compile()(purchase.Purchaser)
}
但是,它不會創建正確的運算式樹,而只會在評估運算式時顯示類似的行為。
uj5u.com熱心網友回復:
要為Expression樹實作 lambda 擴展器(還有其他型別的擴展器可以完成),您需要通過使用XInvoke方法呼叫它們來標記要擴展的 lambda,創建一個ExpressionVisitor來查找呼叫并擴展它們,并使用 commonExpression替換訪問者以將引數應用于XInvoke您正在擴展的 lambda。
public static class ExpandXInvokeExt {
public static TRes XInvoke<TArg1, TRes>(this Expression<Func<TArg1, TRes>> fnE, TArg1 arg1)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1})");
public static TRes XInvoke<TArg1, TArg2, TRes>(this Expression<Func<TArg1, TArg2, TRes>> fnE, TArg1 arg1, TArg2 arg2)
=> throw new InvalidOperationException($"Illegal call to XInvoke({fnE},{arg1},{arg2})");
public static T ExpandXInvoke<T>(this T orig) where T : Expression => (T)new ExpandXInvokeVisitor().Visit(orig);
public static T Evaluate<T>(this T e) where T : Expression => (T)((e is ConstantExpression c) ? c.Value : Expression.Lambda(e).Compile().DynamicInvoke());
/// <summary>
/// ExpressionVisitor to expand a MethodCallExpression of XInvoke with an applied version of the first argument,
/// an Expression.
/// </summary>
public class ExpandXInvokeVisitor : ExpressionVisitor {
public override Expression Visit(Expression node) {
if (node?.NodeType == ExpressionType.Call) {
var callnode = (MethodCallExpression)node;
if (callnode.Method.Name == "XInvoke" && callnode.Method.DeclaringType == typeof(ExpandXInvokeExt)) {
var lambda = (LambdaExpression)(callnode.Arguments[0].Evaluate());
Expression expr = lambda.Body;
for (int argNum = 1; argNum < callnode.Arguments.Count; argNum)
expr = expr.Replace(lambda.Parameters[argNum - 1], callnode.Arguments[argNum]);
return expr;
}
}
return base.Visit(node);
}
}
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static T Replace<T>(this T orig, Expression from, Expression to) where T : Expression => (T)new ReplaceVisitor(from, to).Visit(orig);
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
}
此代碼提供XInvoke了一個和兩個引數,其他的可以以相同的方式添加。
有了這些可用的擴展,你可以ToPurchaseDetailsShallow這樣寫:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallowTemplate
=> (purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.Invoke(purchase.Purchaser)
};
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow => ToPurchaseDetailsShallowTemplate.ExpandXInvoke();
注意:我使用了這個名稱XInvoke,因此編譯器不會將錯誤數量的引數與嘗試呼叫混淆Expression.Invoke(我認為不應該,但確實如此)。
Note: With enough parentheses and casting, you can avoid the template variable, but I am not sure it makes anything better:
public static Expression<Func<Purchase, PurchaseDetailsShallow>> ToPurchaseDetailsShallow
=> ((Expression<Func<Purchase, PurchaseDetailsShallow>>)(
(purchase) => new PurchaseDetailsShallow() {
Price = purchase.Price,
Purchaser = ToPersonDetailsShallow.XInvoke(purchase.Purchaser)
})
)
.ExpandXInvoke();
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/446630.html
