针对接口编程和实体框架4.0

7

我正在尝试弄清楚是否可以在使用Entity Framework 4.0时坚持“针对接口编程,而不是实现细节”的口号。

虽然我找到了一篇关于如何在使用Linq-to-SQL时遵循上述规则的页面(点击此处),但我非常想知道是否可以在使用Entity Framework(也使用Linq)时做到这一点。

以下是通常的用法:

var query = from pages in dataContext.Pages where pages.IsPublished select pages;

foreach (Page page in query)
{
    // do something with page...
    var routeQuery = from routes in page.Route where route.IsValid select routes;

    foreach (Route route in routeQuery)
    {
        // do something with route
    }
}

但我希望像这样使用它:

var query = from pages in dataContext.Pages where pages.IsPublished select pages;

foreach (IPage page in query)
{
    // do something with page...
    var routeQuery = from routes in page.Route where route.IsValid select routes;

    foreach (IRoute route in routeQuery)
    {
        // do something with route
    }
}

基本上,我想通过使用接口将实体框架的DataContext从实例化它的程序集/子系统中传递出去。 提供的所有数据上下文信息都应以接口形式而不是实际类形式呈现。
我想保持实现实体的实际类内部,并仅公开它们实现的接口。
实体框架能否实现这一点? 如果不能,请问还有其他可以以这种方式使用的O / R映射器吗?
如果这不是进一步分离数据库与实际应用程序的好方法,我很乐意听取您的建议。
4个回答

6

我认为更好的解决方案是:

为您的实体数据模型创建存储库,公开ICollection<T>IQueryable<T>

在存储库上使用接口

public interface IRepository
{
   public ICollection<Person> Find(string name); // tighter, more maintanability    
   public IQueryable<Person> Find(); // full power! but be careful when lazy loading

由于接口的存在,您可以轻松替换模拟对象和其他ORM。

public class MockRepo : IRepository
{ 
   public List<Person> persons; // mimics entity set
}

你只能抽象化一定程度。

如果你担心使用与EF绑定的ObjectSet,请使用POCO。

查看我的其他问题以获取更多信息(因为我们现在正在构建这个架构)。

此外,考虑使用依赖注入。在这里,你可以将Repository从管理ObjectContext的业务中分离出来 - 你可以将Repository注入到工作单元中(它是ObjectContext的包装器 - 因此多个Repository或聚合根可以处理相同的上下文)。

在我们的解决方案中,除了Repository之外,没有任何东西会触及Entity Framework(或任何持久性逻辑),而这些Repository位于单独的程序集中。

希望对你有所帮助。


谢谢您的回答。在使用EF时,是否可能让存储库返回一个接口的ICollection,例如ICollection<IPerson>? - Timo Kosig
另一个问题:你将存储库暴露给其余应用程序的程度有多少?是否存在某些逻辑,例如通过应用程序调用的IPersonProvider.GetPerson(string name),最终隐藏存储库,还是应用程序知道IRepository? - Timo Kosig
1
2)目前我正在开发一个ASP.NET MVC应用程序。控制器只与“服务层”通信,UI没有引用存储库DLL。服务层与仓储库通信,其中通过DI(StructureMap)将实例注入到服务层ctor中。是的 - 存储库使用泛型实现。有一个名为IRepository<T>的根接口,它定义了添加、查找、删除等操作。然后在我的DI中,我说“如果有人请求IRepository<Person>,则给他们GenericRepository<Person>。这是另一个实现接口的泛型类。很酷吧? - RPM1984
最终结果是我的控制器会调用 personService.Find(p => p.Name == "Bob");,这将调用服务层(PersonService),然后服务层会执行 repository.Find(predicate).SingleOrDefault();。但是,我喜欢使用修改过的规约模式来定义搜索操作(更少的维护工作、更多的灵活性和功能性,这也是我使用IQueryable的原因)。当然,你不必这样做。这取决于谁在使用你的存储库。 - RPM1984
@Timo Kosig - 如果您的UI无法引用POCO,则存储库将返回什么返回类型?如果您想从Repo返回IPage(例如),那么该接口在哪里声明?在通用的“hub”程序集中吗?对您的架构有点困惑。例如,如果您的Repo正在使用“Page”实体,并且您想返回IPage的集合,则不能只执行db.Pages.ToList()。您需要将其添加到预先声明的ICollection<IPage> list中。正如我所说,您会失去延迟执行/IQueryable。 - RPM1984
显示剩余2条评论

1

您可以按照您提到的L2S示例中的大部分逻辑来实现基于接口的EF Repository类。唯一的主要区别是Repository方法。它有点不同,因为EF在从一个IQueryable转换为另一个IQueryable时无法使用Cast<>。我使用反射来解决这个问题。

以下是针对EF Repository类重新编写的Repository方法示例:

public IQueryable<T> Repository<T>()
    where T : class
{
    if (typeof(T).IsInterface)
    {
        // if T is an interface then get the actual EntityObject Type by calling GetEntityType
        // so that entityType can be used to call back to this method 
        Type entityType = this.GetEntityType<T>();
        MethodInfo mi = this.GetType().GetMethod("Repository");
        // set the T in Repository<T> to be the entity type
        Type[] genericTypes = new Type[] { entityType };
        mi = mi.MakeGenericMethod(genericTypes);
        // call Repository<T>
        object result = mi.Invoke(this, new object[0]);
        return result as IQueryable<T>;
    }
    else
    {
        return this._context.CreateQuery<T>(this.GetEntitySetName<T>());
    }
}

private Type GetEntityType<T>()
{
    if (this.TableMaps.ContainsKey(typeof(T)))
    {
        return this.TableMaps[typeof(T)];
    }
    else
    {
        return typeof(T);
    }
}

@Tom:这看起来对我来说就像是一些黑魔法;-)。我也不确定我完全理解它。这个 this.GetEntityType<T>() 是否需要某种接口到 EntityType 的映射,或者我们的半实体类实现了该接口就足够了? - Timo Kosig
此存储库方法用于测试 T 的类型是否为接口。如果是,则通过调用 GetEntityType 查找实际的 EntityObject 类。GetEntityType 方法通过使用 TableMaps 属性(在 L2S 示例中有解释)来检查 T 的类型是否已映射到 EntityObject。一旦知道了 EntityObject,就会再次通过反射调用 Repository 方法,但这次使用的是 EntityObject 类型而不是接口。然后将第二个 Repository 调用的结果转换为接口类型。 - Tom Brothers
黑魔法是解释这个方法中正在发生的事情的最好方式...但我会尽力让它变得更加清晰易懂。 - Tom Brothers
此存储库方法用于测试 T 的类型是否为接口。如果是,则通过调用 GetEntityType 查找实际的 EntityObject 类。GetEntityType 方法通过使用 TableMaps 属性(在 L2S 示例中有解释)来检查是否已将 T 的类型映射到 EntityObject。一旦知道了 EntityObject,就会再次通过反射使用 EntityObject 类型而不是接口来调用存储库方法。然后将此第二个存储库调用的结果转换为接口类型。 - Tom Brothers
这个程序是否处理连接操作?连接操作是在应用程序中还是在数据库中运行? - Maslow
显示剩余2条评论

0

我已经使用EF 5.0实现了这个。解决方案太复杂了,无法发布。我可以提供一个非常高层次的描述。

仓储的基类看起来像...

public class GenericRepository<TEntityInterface, TEntity> 
    where TDataInterface : TEntityInterface
    where TEntity : class, IBaseEntityInterface, new()

一个继承自这个代码库的子类会长成这样...
public class EmployeeRepository<TEntity> : GenericRepository<IEmployeeEntity, TEntity>, IEmployeeRepository
where TEntity : class, IEmployeeEntity, new()

我使用T4模板自定义实体生成并继承实体接口,或者您可以为每个EF实体创建局部类来继承接口。 我已使用代码生成脚本生成与实体上的属性对应的实体接口。 所有实体都继承IBaseEntityInterface。

在你的代码中的某个点(在我的情况下使用INject注入框架),你将EF实体与存储库结合在一起,如下所示...

Bind<IEmployeeRepository>().To<EmployeeRepository<EmployeeEntity>();

EmployeeEntity是由实体框架生成的。

这种方法存在问题,实体框架不喜欢实体接口之间的LINQ连接,这样做可能会导致错误,具体取决于查询的结构。

您必须在存储库中针对TEntity执行查询。您可以在实体接口(以及存储库中的TEntity)上使用导航属性来有效地执行连接操作。

但是有时您需要执行没有导航属性的连接操作,解决方法是在存储库上公开返回原始IQueryable对象的方法,以便在其他存储库中使用,例如ID查询的IQueryable或代码IQueryable,可以在存储库中创建这些对象,以便另一个存储库将它们包含在查询中。

我认为总的来说,在大型代码库中,使用实体接口而不是直接引用EF实体类的好处超过了使用实体接口与EF一起遇到的问题。除了松散耦合、适应变化等方面的优点外,您还可以将大量代码放在基本存储库中,并针对带有基本实体接口属性的TEntity进行编程。

例如,获取与谓词匹配的实体接口集合的方法,预加载多个属性。

    public IList<TEntityInterface> Where(Expression<Func<TEntityInterface, bool>> predicate, params string[] includedProperties)
    {
        DbQuery<TEntity> query = Context.Set<TEntity>();

        foreach (string prop in includedProperties)
            query = query.Include(prop);

        return query.Where(predicate).ToList<TEntityInterface>();
    }

就我而言,在代码中引用实体框架实体基本上是将代码与EDMX以及特定的数据库模式紧密耦合在一起。如果您需要使用相同的代码库支持多个数据库模式,则实体框架默认情况下会让您感到非常困难。
希望在未来的EF版本中,这些问题将得到解决,我们可以成为良好的程序员,使用接口而不是EF类,而不需要这些技巧。

0
你可以创建需要接口而不是具体对象的存储库,并使用EF生成的类的部分类实现这些接口。然而,这是否真正值得努力,我不确定。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接