2017-03-01 143 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); 
    } 
} 
相关问题