用自定义DbSet / IDbSet包装DbSet <tentity>?</tentity>

时间:2011-09-15 13:37:36

标签: c# design-patterns architecture entity-framework-4.1 abstraction

首先,我认为这样做有些荒谬,但是我团队的其他成员坚持认为,除了“我认为这是愚蠢的”之外,我无法提出反对它的好论据......

我们要做的是创建一个完全抽象的数据层,然后对该数据层进行各种实现。很简单吧?输入Entity Framework 4.1 ...

我们的最终目标是程序员(我尽力只留在数据层)永远不希望暴露在具体的类中。他们只想在代码中使用接口,除了显然需要实例化工厂。

我希望实现以下内容:

首先我们有所有接口的“Common”库,我们称之为“Common.Data”:

public interface IEntity
{
    int ID { get; set; }
}

public interface IUser : IEntity
{
    int AccountID { get; set; }
    string Username { get; set; }
    string EmailAddress { get; set; }
    IAccount Account { get; set; }
}

public interface IAccount : IEntity
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?
}

public interface IEntityFactory
{
    DbSet<IUser> Users { get; }
    DbSet<IAccount> Accounts { get; }
}

然后我们有了一个实现库,我们称之为“Something.Data.Imp”:

internal class User : IUser
{
    public int ID { get; set; }
    public string Username { get; set; }
    public string EmailAddress { get; set; }
    public IAccount Account { get; set; }

    public class Configuration : EntityTypeConfiguration<User>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

internal class Account : IAccount
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?

    public class Configuration : EntityTypeConfiguration<Account>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

厂:

public class ImplEntityFactory : IEntityFactory
{
    private ImplEntityFactory(string connectionString) 
    {
        this.dataContext = new MyEfDbContext(connectionString);
    }
    private MyEfDbContext dataContext;

    public static ImplEntityFactory Instance(string connectionString)
    {
        if(ImplEntityFactory._instance == null)
            ImplEntityFactory._instance = new ImplEntityFactory(connectionString);

        return ImplEntityFactory._instance;
    }
    private static ImplEntityFactory _instance;

    public DbSet<IUser> Users // OR IDbSet<IUser> OR [IDbSet implementation]?
    { 
        get { return dataContext.Users; }
    }

    public DbSet<IAccount> Accounts // OR IDbSet<IUser> OR [IDbSet implementation]?
    {
        get { return dataContext.Accounts; }
    }
}

上下文:

public class MyEfDataContext : DbContext
{
    public MyEfDataContext(string connectionString)
        : base(connectionString)
    {
        Database.SetInitializer<MyEfDataContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new User.Configuration());
        modelBuilder.Configurations.Add(new Account.Configuration());
        base.OnModelCreating(modelBuilder);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Account> Accounts { get; set; }
}

然后前端程序员将使用它,如:

public class UsingIt
{
    public static void Main(string[] args)
    {
        IEntityFactory factory = new ImplEntityFactory("SQLConnectionString");
        IUser user = factory.Users.Find(5);
        IAccount usersAccount = user.Account;

        IAccount account = factory.Accounts.Find(3);
        Console.Write(account.Users.Count());
    }
}

所以这就是它......我希望这里的某个人能够指出我正确的方向,或者帮我解决一个好的争论,我可以回到开发团队。我看过这个网站上的其他一些文章,关于EF无法使用界面和one reply说你无法实现IDbSet(我觉得有点好奇,为什么他们会提供如果你不能实现它?)但到目前为止无济于事。

提前感谢您的帮助! Ĵ

1 个答案:

答案 0 :(得分:45)

第一个参数是EF不能与接口一起使用。必须使用真实实体实现来定义DbSet

第二个参数是你的实体不应该包含DbSet - 这是与上下文相关的类,你的实体应该是纯粹的依赖,除非你要实现Active记录模式。即使在这种情况下,您也绝对无法访问另一个实体中不同实体的DbSet。即使你使用set,你仍然太靠近EF,实体永远不会拥有访问另一个实体类型的所有实体的属性(不仅仅是那些与当前实例相关的实体)。

只是为了清楚说明EF中的DbSet具有非常特殊的含义 - 它不是一个集合。它是数据库的入口点(例如DbSet命中数据库上的每个LINQ查询),并且它在正常情况下不在实体上公开。

第三个参数是每个应用程序使用一个上下文 - 每个singleton工厂只有一个私有实例。除非您正在执行单个运行批处理应用程序it is definitely wrong

最后一个论点很实用。您需要付费才能提供功能,而不是浪费时间进行抽象,这不会给您(和您的客户)带来任何商业价值。它不是为了证明为什么你不应该创建这种抽象。这是为了证明你为什么要这样做。使用它会得到什么价值?如果你的同事无法提出具有商业价值的论据,你可以直接找你的产品经理让他使用他的权力 - 他持有预算。

通常,抽象是精心设计的面向对象应用程序的一部分 - 这是正确的。但是:

  • 每一次抽象都会使你的应用程序变得更加复杂,并且会增加开发的成本和时间
  • 并非每一个抽象都会使您的应用程序更好或更易于维护 - 过多的抽象会产生相反的效果
  • 摘要EF很难。假设您将以可以将其替换为另一个实现的方式抽象数据访问,这是数据访问专家的任务。首先,您必须拥有许多数据访问技术的非常好的经验,才能定义这样的抽象,这将适用于所有这些抽象(最后,您只能告诉您的抽象适用于您在设计时所考虑的技术) )。您的抽象仅适用于EF DbContext API,而不是其它因为它不是抽象。如果你想构建通用抽象,你应该开始研究存储库模式,工作单元模式和规范模式 - 但这是制作它们并实现它们的大量工作。需要的第一步是隐藏与抽象背后的数据访问相关的所有内容 - 包括LINQ!
  • 仅在您现在需要时才提取数据访问以支持多个API。如果你认为它在未来比在业务驱动的项目中有用,那么完全错误的决策和那些带有这个想法的开发人员无法做出业务目标决策。

什么时候做“很多”抽象是有意义的?

  • 您现在有这样的要求 - 将此类决定的负担转移给负责预算/项目范围/要求等的人员。
  • 现在需要抽象来简化设计或解决一些问题
  • 您正在进行开源或业余爱好项目,并不是出于业务需求,而是出于项目的纯度和质量。
  • 您正在开发平台(长寿的零售产品,可以存活很长时间)或公共框架 - 这通常会回到第一点,因为这类产品通常具有这样的抽象要求

如果您只使用目标应用程序(主要是按需提供单一用途的应用程序或外包解决方案),则只应在必要时使用抽象。这些应用程序受成本驱动 - 目标是在最短的时间内以最低的成本提供工作解决方案。即使最终的应用程序内部不是很好,也必须实现这个目标 - 唯一重要的是应用程序是否符合要求。基于“如果......发生了什么”或“可能我们将需要......”的任何抽象都会增加虚拟(非现有)需求的成本,这些需求将永远不会发生,并且在大多数情况下与客户的初始合同不计这样的额外费用。

顺便说一下。这种类型的应用程序是MS API和设计师策略的目标 - MS将使许多设计师和代码生成器创建非最佳但廉价和快速的解决方案,这些解决方案可以由技能较小且非常便宜的人创建。最后一个例子是LightSwitch。