包装DbSet< TEntity>使用自定义DbSet/IDbSet

本文关键字:DbSet 自定义 IDbSet TEntity 包装 | 更新日期: 2023-09-27 18:08:35

首先,我认为这样做有点荒谬,但我团队的其他成员坚持这样做,我想不出一个好的理由来反对它,除了"我认为这很愚蠢"…

我们试图做的是创建一个完全抽象的数据层,然后有该数据层的各种实现。很简单,对吧?进入实体框架4.1…

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

我想实现以下目标:

首先,我们有所有接口的"Common"库,我们将其称为"Common"。数据":

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不能使用接口的文章,其中一个回复说你不能实现IDbSet(我觉得有点奇怪,如果你不能实现它,他们为什么要提供它?),但到目前为止没有任何帮助。

提前感谢您的帮助!J

包装DbSet< TEntity>使用自定义DbSet/IDbSet

第一个参数是EF不适用于接口。DbSet必须定义为一个真实的实体实现。

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

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

第三个参数是每个应用程序使用一个上下文——每个单例工厂有一个私有实例。除非你正在做一些单次运行的批处理应用程序,否则这绝对是错误的。

最后一个参数很实用。你是为交付特性而付费的,而不是浪费时间在抽象上,因为抽象不会给你(和你的客户)带来任何商业价值。这并不是要证明为什么不应该创建这个抽象。而是要证明你为什么要这么做。你将从使用它中获得什么价值?如果你的同事不能提出有商业价值的论点,你可以简单地去找你的产品经理,让他行使他的权力——他掌握着预算。

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

  • 每个抽象都会让你的应用变得更加复杂,并且会增加开发成本和时间
  • 并不是每一个抽象都能让你的应用变得更好或更易于维护——太多的抽象会产生相反的效果
  • 抽象EF是困难的。说你将抽象数据访问的方式,你可以用另一种实现取代它是数据访问专家的任务。首先,你必须对许多数据访问技术有很好的经验,以便能够定义与所有这些技术一起工作的抽象(最后,你只能告诉你的抽象与你设计时考虑的技术一起工作)。您的抽象将只能与EF DbContext API一起工作,而不能与其他任何东西一起工作,因为它不是一个抽象。如果你想构建通用的抽象,你应该开始学习存储库模式、工作单元模式和规范模式——但是要使它们和实现它们的通用是一项很大的工作。第一步需要将所有与数据访问相关的内容隐藏在抽象之后——包括LINQ!
  • 抽象数据访问以支持多个api只有在你现在需要的时候才有意义。如果你只认为它在未来有用,而不是在业务驱动的项目中有用,那就大错特错了,抱着这种想法的开发者根本无法做出业务目标决策。

什么时候做"大量"抽象是有意义的?

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

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

顺便说一句。这种类型的应用程序是微软api和设计师策略的目标——微软将制作许多设计师和代码生成器,这些设计器和代码生成器将创建非最佳但便宜且快速的解决方案,这些解决方案可以由技能较低的人创建,并且非常便宜。最后一个例子是LightSwitch。