通过接口

时间:2015-10-30 19:12:26

标签: c# entity-framework generics dependency-injection

背景

我有一个多层应用程序,我正在尝试重构。我的数据层本质上只是一个EF数据库上下文(及其所有实体)。 我想要实现的目标之一是删除我的Biz层和我的数据层之间的依赖关系(使用依赖注入),以便我的单元测试不必命中数据库。为此,我创建了一个我的上下文实现的简单接口IDataStore。

public interface IDataStore
{
   void Insert<T>(T entity) where T:class;
   void Update<T>(T entity) where T:class;
   void Delete<T>(T entity) where T:class;
   T GetById<T>(int id) where T:class;
   IQueryable<T> All<T>() where T:class;
}

public partial class MyContext:DbContext,IDataStore
{
   public void Insert<T>(T entity) where T:class{
      this.Set<T>().Add(entity);
      this.SaveChanges();
   }
   ...
   public IQueryable<T> All<T>() where T:class{
      return this.Set<T>();
   }
}

使用IoC contianer,我能够将我的Biz层中的所有引用删除到MyContext(而不是引用IDataStore),但我仍然无法引用我的特定实体类。在下面的例子中,我使用的是IDataStore,但我也使用了Data.Color(我的一个实体类)。

public static void MyBizMethod(){
   ...
   IDataStore store = myUnityContainer.Resolve<IDataStore>();
   List<string> colorNames = store.All<Data.Color>().Select(c => c.name).ToList();
   ...
}

我接下来要做的是创建一个IColor接口并使用一个分部类来使Data.Color实现IColor。然后,我修改了我的Biz层逻辑,以调用DataStore.All&lt; IColor&gt;()代替DataStore.All&lt; Data.Color&GT;();

public interface IColor{
   string name;
}

public partial class Color: IColor { }

并在我的商务层......

public static void MyBizMethod(){ 
   ...
   List<string> colorNames = store.All<IColor>().Select(c => c.name).ToList();
   ...
}

问题

当DbContext.Set&lt;使用T接口调用T&gt;()(在我的情况下是IColor)我得到一个异常,因为上下文有一个DbSet&lt;色&GT;不是DbSet&lt; ICOLOR取代。

是否有某种情况使上下文具有DbSet&lt; ICOLOR&GT;代替?如果没有,是否有其他方法通过接口从DbContext通用检索DbSet?我真的很想避免写一个巨大的转换声明。

或者......有没有更好的方法来完成这项工作?优选地,不涉及为我拥有的每种类型的实体创建单独的界面,

更新

@doctor留下的帖子解决了我的初始问题(如何通过界面查询表),但是其他帖子解释了甚至没有必要(因为我的实体类已经抽象了),我觉得他们帮助了更多与我的问题的根源

尽管如此,我想看看其他帖子的解决方案是否适用于我最初提出的问题,而且它确实......在大多数情况下都是如此。值得注意的例外是IQueryable的.Include()方法。

请考虑以下代码示例:

var people = store.All<IPerson>().Where(p => p.IsAlive).Include("Children").ToList();
foreach (IPerson parent in people)
{
   Console.WriteLine(parent.Name + " has " + p.Children.Count() + " children");
}

调用store.All&lt; Person&gt;(),.Include()会按预期转换为SQL。然而,当使用IPerson时,它似乎被完全忽略,并且每次循环都会生成单独的查询。有趣的是.Where()子句在两种情况下都被正确翻译(我使用SQL分析器验证)。无论如何,我决定不为我拥有的每个实体实现单独的接口,而是直接引用实体。只想指出奇怪的.Include()行为。

1 个答案:

答案 0 :(得分:6)

大多数情况下,当使用Entity Framework时,人们不会将业务逻辑放在实体本身上。他们像您的“商务层”类一样使用服务来使用这些实体上显示的数据。

如果您遵循此模式,则实体不需要模拟,因为它们是数据结构,而不是服务。您可以轻松地创建它们的真实实例。

此外,实体框架中的类类型本身代表了实际的数据集合:您可能有一些实现相同的接口,但当您说“我想要Color的集合时“你要求数据库中特定表格中的那些。由于您已经与数据存储中存在的一组Color概念紧密耦合,因此您不可能通过使用接口在此级别表示它们来获得任何收益。