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

时间:2010-10-14 08:28:54

标签: c# .net entity-framework interface entity-framework-4

我正在试图弄清楚是否有可能坚持“针对接口而不是实现的程序”的口头禅。使用Entity Framework 4.0。

虽然我找到了一个页面,解释了如何在使用Linq-to-SQL(look here)时坚持上述规则,但我非常想知道是否可以使用实体框架(也是实体框架)使用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 个答案:

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

只有这么多你才能抽象出来。

如果您担心使用ObjectSet(与EF绑定),请使用POCO。

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

另外,请考虑使用依赖注入。在这里,您可以从管理ObjectContext的业务中获取存储库 - 您可以将存储库注入工作单元(这是ObjectContext的包装器 - 因此多个存储库或聚合根可以处理相同的上下文)。

在我们的解决方案中,除了存储库之外,没有任何内容涉及实体框架(或任何持久性逻辑),这些存储库位于单独的程序集中。

HTH。

答案 1 :(得分:1)

您可以按照您提到的L2S示例中的大多数逻辑来实现基于接口的EF Repository类。一个主要的区别是Repository方法。它有点不同,因为EF不适用于Cast&lt;&gt;从一个IQueryable转换到另一个。我用反射来解决这个问题。

以下是为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);
    }
}

答案 2 :(得分:0)

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

答案 3 :(得分: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 templates来自定义实体的生成以继承实体接口,或者您可以为每个EF实体创建部分类以继承接口。我已经使用代码生成脚本为与实体属性相关的实体生成接口。所有实体都继承IBaseEntityInterface。

在您的代码中的某个时刻(在我的情况下使用INject注入框架),您将EF实体与存储库结合起来......

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

实体框架生成EmployeeEntity的地方。

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

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

然而,有时您会想要在没有导航属性的情况下进行连接,解决方法是在存储库上公开返回原始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类。