【问题标题】:Wrapping DbSet<TEntity> with a custom DbSet/IDbSet?用自定义 DbSet/IDbSet 包装 DbSet<TEntity>?
【发布时间】:2011-11-17 21:46:03
【问题描述】:

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

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

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

我想实现以下目标:

首先,我们有所有接口的“通用”库,我们称之为“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(我觉得有点奇怪,如果你能,他们为什么要提供它'不实现它?)但到目前为止无济于事。

提前感谢您的帮助! J

【问题讨论】:

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


【解决方案1】:

第一个论点是 EF 不适用于接口。 DbSet 必须使用真实的实体实现来定义。

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

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

第三个参数是每个应用程序使用一个上下文 - 每个单例工厂都有一个私有实例。除非你正在做一些单次运行的批处理应用程序it is definitely wrong

最后一个论点很实用。你是因为交付功能而不是浪费时间在抽象上,这不会给你(和你的客户)任何商业价值。这不是要证明为什么你不应该创建这个抽象。这是为了证明你为什么应该这样做。你会从使用它中获得什么价值?如果您的同事无法提出具有商业价值的论点,您可以直接去找您的产品经理,让他使用他的权力 - 他掌握着预算。

一般来说,抽象是精心设计的面向对象应用程序的一部分——这是正确的。但是:

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

什么时候做“大量”抽象才有意义?

  • 您现在有这样的要求 - 将此类决定的负担转移到负责预算/项目范围/要求等的人员身上。
  • 您现在需要抽象来简化设计或解决某些问题
  • 您正在做开源或爱好项目,您的驱动力不是业务需求,而是项目的纯度和质量
  • 您正在平台(长期存在的零售产品,将长期存在)或公共框架上工作 - 这通常会回到第一点,因为这类产品通常具有要求这样的抽象

如果您只使用目标应用程序(主要是按需应用程序或外包解决方案的单一用途),则应仅在必要时使用抽象。这些应用程序是由成本驱动的——目标是以最低的成本和最短的时间提供有效的解决方案。即使最终的应用程序在内部不是很好,也必须实现这个目标——唯一重要的是应用程序是否满足要求。任何基于“如果……发生会怎样”或“也许我们需要……”的抽象会因虚拟(不存在)需求而增加成本,这在 99% 的情况下永远不会发生,并且在大多数情况下,与客户的初始合同不计算在内其中这样的额外费用。

顺便说一句。这种类型的应用程序是 MS API 和设计器策略的目标 - MS 将制造大量设计器和代码生成器,它们将创建非最优但便宜且快速的解决方案,这些解决方案可以由技能较少且非常便宜的人创建。最后一个例子是 LightSwitch。

【讨论】:

  • 首先,我完全同意拉迪斯拉夫的观点。尤其是“这是为了证明你为什么应该这样做”。同意!模式追逐是 EF4.1 的新技巧和令人头疼的秘诀。与调用存储库并利用您的类作为 POCO 的服务的接口。
  • 非常感谢你们两位的明智之言!我曾怀疑过很多相同的事情......无论出于何种原因(可能是因为我作为 DBA 的根源),我似乎是这里唯一认为太多抽象不适用于数据层的工程师。
  • 如果需要抽象本身并增加价值,那么抽象本身不是问题,但它有成本和复杂性。
  • 您回答的第一行正是我要找的!谢谢
  • 到目前为止,EF 确实可以使用接口 IDbSet!对不起
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-01
相关资源
最近更新 更多