2014-07-04 65 views
15

首先,規格。我們使用MVC5,.NET 4.5.1和Entity framework 6.1。有沒有辦法在lambda表達式樹中使用`dynamic`?

在我們的MVC5商業應用程序中,我們有很多重複的CRUD代碼。我的工作是「自動化」大部分,這意味着將其提取到基類並使其可重用。現在,我擁有控制器,視圖模型和EF6實體模型的基類。

我的抽象基類,所有EF6實體繼承:

public abstract class BaseEntity<TSubclass> 
    where TSubclass : BaseEntity<TSubclass> 
{ 
    public abstract Expression<Func<TSubclass, object>> UpdateCriterion(); 
} 

UpdateCriterion方法是在數據庫方面的AddOrUpdate方法使用。我有一個通用的子類參數,因爲UpdateCriterion需要返回使用確切的子類類型,而不是接口或基類的lambda表達式。以極其簡單的子類實現這個抽象基類是這樣的:

public class Worker : BaseEntity<Worker> 
{ 
    public int ID { get; set; } 
    public int Name { get; set; } 

    public override Expression<Func<Worker, object>> UpdateCriterion() 
    { 
     return worker => worker.ID; 
    } 
} 

之後,在我的基本控制器的SaveOrUpdate行動,我想有這樣的代碼:

public ActionResult Save(TViewModel viewModel) 
{ 
    if (ModelState.IsValid) 
    { 
     var entityModel = viewModel.ConstructEntityModel(); 
     db.Set<TEntityModel>().AddOrUpdate<TEntityModel>(entityModel.UpdateCriterion(), entityModel); 
     db.SaveChanges(); 
    } 
} 

感謝的是,基礎控制器的子類本身不需要像以前那樣實現Save方法。現在,所有這些工作,儘管時髦的語法,它實際上工作得很好(我的意思是,class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>,認真?)。

這是我的問題。對於大多數實體字段ID是關鍵,但對於一些它不是,所以我無法用超類實現正確地概括。所以現在,每個實體子類實現它自己的UpdateCriterion。但是,因爲對於大多數(90%以上)實體來說e => e.ID是正確的實現,所以我有很多重複。所以我想給實體基類改寫爲這樣的:

public abstract class BaseEntity<TSubclass> 
    where TSubclass : BaseEntity<TSubclass> 
{ 
    public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
    { 
     return entity => ((dynamic)entity).ID; 
    } 
} 

的目的是提供使用ID爲重點的默認實現,並允許如果他們使用不同的密鑰子類來覆蓋它。我不能使用具有ID字段的接口或基類,因爲並非所有實體都擁有它。我以爲我會用dynamic拉出ID字段,但我得到以下錯誤:Error: An expression tree may not contain a dynamic operation

那麼,有關如何做到這一點的任何想法?反射在基地UpdateCriterion工作?

回答

1

不,您不能在Linq to Entities查詢中使用動態。 但是,您可以在運行時構建Lambda表達式。

public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
{ 
    var param = Expression.Parameter(typeof(TSubclass)); 
    var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object)); 

    return Expression.Lambda<Func<TSubclass, object>>(body, param); 
} 

如果TSubclass類型沒有ID屬性Expression.Property(param,「ID」)會拋出異常。

此外,您可以使用實體模型中的MetadataWorkspace獲取TSubclass的主鍵列。

+0

謝謝,這正是我需要的! – Davor

+0

哦,另外,你知道如何在身體部分添加多個字段嗎?像'e => new {e.ID,e.Name}'? – Davor

+0

你知道如何用Expression類創建'e => new {e.ID,e.Name}?'嗎? – codeworx

1

如果您正在定義BaseEntity類,則可以添加一個虛擬只讀屬性,該屬性返回實際的ID屬性。我相信EF會將只讀屬性視爲「計算」,因此它們不會存儲到數據庫中。

public abstract class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass> 
{ 
    public abstract object ID { get; } 
    public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
    { 
     return entity => entity.ID; 
    } 
} 

public partial class Foo : BaseEntity<Foo> 
{ 
    public Int32 FooId { get; set; } 
    public override object ID { get { return FooId; } } 
} 

只是一個想法 - 我只試過在LinqPad編譯和檢查一個調用UpdateCriterion的值。 :)

1

看看this answer。它使用反射來獲取ID屬性。我認爲它可以解決你的問題:

public static object GetPropValue(object src, string propName) 
{ 
    return src.GetType().GetProperty(propName).GetValue(src, null); 
} 

然後,通過

return entity => GetPropValue(entity, "ID"); 

更換您的lambda表達式我沒有測試,因爲我沒有完全的代碼工作進行測試。如果有效,請告訴我們。

+1

我很肯定他不需要價值,他需要表達。 – codeworx

+0

沒問題。一個Lambda Exp可以有方法調用(除了一些特定的情況,例如編譯爲SQL的表達式) –

+0

是的,但是在這種情況下,結果用於DbSet的AddOrUpdate方法。而這個方法需要PropertyExpression。 – codeworx

相關問題