避免代码重复几乎相同的逻辑应用于稍微不同的实体类?

时间:2017-03-01 15:20:06

标签: entity-framework interface linq-to-entities entities

假设我有一个数据库支持的菜单,使用带有“Item”和“Category”实体的Entity Framework。

我有两个类(ItemMappingState和CategoryMappingState)几乎完全相同并执行相同的操作(管理项目和类别的外部映射),但是一个使用Item,另一个使用Category。类之间唯一真正的区别(以及阻止我根据接口使管理器类通用的原因)是Item有一个名为MappedItems的属性,Category有一个名为MappedCategories的属性。

由于在Linq-To-Entities查询中使用了这些属性,我认为我不能创建任何类型的公共接口,因为接口方法/属性将无法在Linq-To-Entities中正确转换在运行时查询(如果我错了,请纠正我)。

我真的很烦我,我有两个相当大的类几乎相同,一行一行,我不能将它重构为一个适用于这两种类型的类,因为它们是实体类型。我想我可能会为不同的行添加运行时类型检查并转换为具体类型,但我仍然需要一个接口来处理所有其他相同的属性。

这里的底线是,是否可以为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方法必须确认一个独立的查询接口类型,但是由具体的实体类型实现。本质上,它将接口与实体类型分离并指定两者,以便一个标识表,另一个标识查询接口。

1 个答案:

答案 0 :(得分: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); 
    }
}