2017-03-01 68 views
0

假設我有一個使用Entity Framework和「Item」和「Category」實體的數據庫支持的菜單。爲幾乎相同的邏輯避免代碼重複應用於略有不同的實體類?

我有兩個類(ItemMappingState和CategoryMappingState),它們幾乎完全相同並且執行相同的操作(管理項目和類別的外部映射),但其中一個使用Item,另一個使用Category。這兩個類之間唯一真正的區別(以及阻止我僅僅根據接口創建管理器類的通用類)是Item有一個名爲MappedItems的屬性,Category有一個名爲MappedCategories的屬性。

由於在Linq-To-Entities查詢中使用了這些屬性,我不認爲我可以製作任何類型的通用接口,因爲在Linq-To-Entity查詢中接口方法/實體在運行時查詢(糾正我,如果我錯了)。

這真的讓我感到困擾,我有這兩個相當大的類,它們幾乎完全相同,排成一行,我不能將它重構成一個可以與這兩種類型一起工作的類,因爲它們是實體類型。我想我可能會添加一個運行時類型檢查線的不同和轉換爲具體的類型,但我仍然需要一個接口的所有其他屬性是相同的。

這裏的底線是是否有可能爲EF類型創建通用接口,並且實際上能夠在Linq-To-Entities查詢中使用它們。

例如:

interface IItems { 
    Guid ID {get;set;} 
    ICollection<IID> Items {get;set;} 
} 

interface IID { 
    Guid ID {get;set;} 
} 

class A: IItems { 
    public Guid ID {get;set;} 
    public ICollection<AItem> Items {get;set;} //Navigation property 
} 

class AItem: IID { 
    public Guid ID {get;set;} 
} 

class B: IItems { 
    public Guid ID {get;set;} 
    public ICollection<BItem> Items {get;set;} //Navigation property 
} 

class BItem: IID { 
    public Guid ID {get;set;} 
} 

class AManager { 
    public IEnumerable<Guid> GetItemIDs(Guid id) { 
     return DbContext.Set<A>().FirstOrDefault(x => x.ID == id).Select(x => x.Items.ID); 
    } 
} 

class BManager { 
    public IEnumerable<Guid> GetItemIDs(Guid id) { 
     return DbContext.Set<B>().FirstOrDefault(x => x.ID == id).Select(x => x.Items.ID); 
    } 
} 

正如你所看到的,AManager和BManager幾乎是一樣的。他們做同樣的事情;它們具有相同的簽名,它們有效地運行具有相同屬性名稱的相同查詢。問題在於他們從兩個不同的表中提取數據,因此從DbContext獲取Set時必須使用具體類型。由於這種情況一定是這樣,所以查詢不能用像IID這樣的通用接口來編寫。我希望做的是有一個經理是這樣的:

class Manager<TQueryInterface,TEntity> where TQueryInterface:IItems where TEntity:IItems { 
    public IEnumerable<Guid> GetItemIDs() { 
     return DbContext.Set<TQueryInterface,TEntity>().FirstOrDefault(x => x.ID == id).Select(x => x.Items.ID); //query written against TQueryInterface, but runs against table for TEntity in the database 
    } 
} 

或者只是

class IItemsManager<TEntity> 
    where TEntity:IItems 
{ 
    public IEnumerable<Guid> GetItemIDs() 
    { 
    //query written against IItems, but runs against table for TEntity in the database 
    return DbContext 
     .Set<IItems,TEntity>() 
     .FirstOrDefault(x => x.ID == id) 
     .Select(x => x.Items.ID); 
    } 
} 

這是一個簡單的例子,但說明了問題。似乎沒有辦法編寫結構相同但在EF中的不同表上執行操作的複雜查詢,因爲DbSet(以及查詢本身)必須綁定到具體類型,而不是通用接口。 Set方法必須確認一個與具體實體類型分離的查詢接口類型,但由具體的實體類型實現。實質上,它將接口從實體類型中分離出來並指定兩者,以便標識表和其他標識查詢接口。

+0

本文聲稱實體上的接口是一種反模式,但在這種情況下,實際上有兩個不同的類,它們幾乎需要一個通用的接口,幾乎是property-for-property,並且它允許單個類在任何一個類上運行邏輯他們,消除代碼重複。 – Triynko

+0

本質上,我試圖爲接口編寫Linq-To-Entities查詢,但讓它們針對基礎具體類型的表運行。例如:IRepository ()。GetQuery()。選擇(x => x.ID),以便它從ICatalogType接口表示的具體實體中選擇ID。也許我可以創建一個接受兩個泛型參數的接口以及底層具體類型的IRepository版本? – Triynko

+0

挖掘框架,我甚至不知道是否有可能爲接口獲取DbSet ,因爲有一個名爲「CannotCallGenericSetWithProxyType」的錯誤字符串。我不確定如何解釋它。 – Triynko

回答

0

我有代碼(已更名爲單位),最有可能可以爲您的使用進行修改:

public interface IProductDO { } 
public class Product1DO : IProductDO { } 
public class Product2DO : IProductDO { } 

public async Task<IProductDO> GetProductAsync(Expression<Func<IProductDO, bool>> predicate) 
{ 
    var result = await _context.Product1s 
    .Where(predicate) 
    .AsNoTracking() 
    .FirstOrDefaultAsync() as Product1DO; 
} 

所以我想你可以做到這一點還是非常類似的東西:

class IItemsManager<TEntity> 
    where TEntity:IItems 
{ 
    public IEnumerable<Guid> GetItemIDs() 
    { 
    return DbContext 
     .Set<TEntity>() 
     // this cast may not even be necessary 
     // depends on if the linq query is aware of the generic constraint 
     .Where<IItems>(x => x.ID == id) 
     .FirstOrDefault() 
     .Select(x => x.Items.ID); 
    } 
} 
相關問題