使用存储库模式时的多个数据库上下文

Multiple database contexts when using repository pattern

我现在有点迷茫...我从未见过关于解决问题的如此多的不同信息。但让我们从头开始。

由于 Ninject,我正在使用 ASP.NET 将存储库注入控制器的 MVC。我有 2 个简单的实体:带有已创建博客条目列表的管理员和带有一个虚拟管理字段的条目。

管理员:

public class Admin
{
    [Key, ScaffoldColumn(false)]
    public int Id { get; set; }

    [Required(ErrorMessage = "Zły login.")]
    [StringLength(20), MinLength(3)]
    [RegularExpression(@"^[a-zA-Z0-9]*$", ErrorMessage = "Special characters are not allowed.")]
    public string Login { get; set; }

    [Required(ErrorMessage = "Złe hasło.")]
    [StringLength(20, MinimumLength = 3)]
    [DataType(DataType.Password)]
    [Display(Name = "Hasło")]
    public string Password { get; set; }

    public virtual List<Entry> CreatedEntries { get; set; } // napisane aktualności przez danego admina
}

条目:

public class Entry
{
    [Key, ScaffoldColumn(false)]
    public int Id { get; set; }

    [StringLength(200, MinimumLength = 2)]
    [DataType(DataType.Text)]
    [Display(Name = "Tytuł")]
    public string Title { get; set; }

    [Required, StringLength(2000), MinLength(3)]
    [Display(Name = "Treść")]
    [UIHint("tinymce_jquery_full"), AllowHtml]
    public string Text { get; set; }

    public virtual Admin Admin { get; set; }
}

您可能知道它的去向,因为这个问题是... "classic" 在 Whosebug 上。

在控制器中,我想将一个对象绑定到另一个对象:

entry.Admin = repAdmins.GetAdmin(User.Identity.Name);

repEntries.AddEntry(entry);

在存储库中:

public void AddEntry(Entry entry)
    {
        db.Entries.Add(entry);
        db.SaveChanges();
    }

当然我不能那样做,因为著名的 "An entity object cannot be referenced by multiple instances of IEntityChangeTracker",这是每个存储库中有单独的数据库上下文的结果。

当我在寻找解决方案时,我已经知道解决它的最佳方法可能是使用一个公共上下文。然后我发现了工作单元模式。但真正的问题开始了。

  1. 在许多站点上,对此的解决方案略有不同。
  2. 存储库必须有通用的通用接口(我不想使用它,因为我不需要对每个实体进行每个 CRUD 操作,而且有时我需要有额外的方法,例如 "IfExists", 等等)
  3. 在一些网站上,我读到不需要整个抽象,因为 Entity Framework 已经提供了抽象并且 UoW 在 DbContext 中实现(无论那意味着什么)
  4. 工作单元模式(至少从互联网上的例子来看)对我来说似乎是一个真正的痛苦...

我需要一些指导...我只学习 ASP.NET MVC 一年。对我来说,它似乎是 "triumph of form over content"。因为... 我只需要将一个对象绑定到另一个对象。 我开始认为当我在控制器中只有一个上下文对象时更好,但我没有不需要建造埃菲尔铁塔来实现上面提到的:\但是我喜欢存储库的想法...

我将直接回答问题作为开场白。简单地说,您的存储库应该将上下文作为依赖项(它应该有一个构造函数来接受类型为 DbContext 的参数)。您的上下文应该由 Ninject 管理,然后注入您的存储库 and/or 您的控制器。这样,一切都始终使用相同的上下文。您应该在 "request" 范围内执行所有这些操作,以便上下文特定于当前请求。

也就是说,我想谈谈你的其他一些观点。首先,存储库只是一种访问方法。它真的不应该依赖于实体。可以使用您不打算在特定实体上使用的方法:只是不要使用它们。但是,如果您确实想强制执行此操作,则始终可以使用通用约束和接口。例如,假设您不希望对特定实体进行更新。你可以有这样的接口:

public interface ICreateable
{
}

public interface IUpdateable : ICreateable
{
}

然后,您不应更新的实体将仅实现 ICreateable,而其他实体(允许更新)将实现 IUpdateable(通过接口继承,也实现 ICreateable ).最后,您将对存储库方法添加约束:

public void Create<TEntity>(TEntity entity)
    where TEntity : class, ICreateable

public void Update<TEntity>(TEntity entity>)
    where TEntity : class, IUpdateable

因为,有问题的实体只实现了 ICreatable,它没有资格用作 Update 的类型参数,所以没有办法使用那个方法。

接下来,建议不要将 repository/UoW 模式与 Entity Framework 一起使用确实是因为 Entity Framework 已经实现了这些模式。存储库模式作为一种在一个地方包含所有数据库查询逻辑(构造 SQL 语句等)的方式而存在。这就是我们在这里谈论的"abstraction"。换句话说,不是在您的应用程序代码中直接构建 SQL 语句,而是将该代码 抽象化 到存储库中。然而,这正是 Entity Framework 所做的,这就是为什么您不需要再次这样做的原因。工作单元模式作为一种编排多个存储库工作的方法而存在,允许事务之类的事情。然而,再一次,Entity Framework 完成了这一切。

添加任何进一步抽象的唯一原因是如果您想抽象实际的提供者,即 Entity Framework 本身。例如,您可以有一个像 IRepository 这样的接口,然后创建像 EntityFrameworkRepositoryNHibernateRepositoryWebApiRepository 等这样的实现。您的应用程序将永远只依赖于 IRepository,然后您可以根据需要分入不同的实现。如果您不打算这样做,或者您将始终使用 Entity Framework,那么您最好直接使用您的上下文。任何进一步的抽象都只是其他需要维护的东西,对您的应用程序没有任何好处。

最后,是的,工作单元模式对每个人来说都是一个真正的痛苦,不仅仅是你。这就是为什么我完全放弃它。我使用我称之为 "truly generic repository" 的东西,它利用通用方法和接口来处理我想扔给它的任何实体。这意味着它不仅充当存储库,而且还是工作单元。每个上下文只需要一个实例,并且它与提供者无关。有关详细信息,请查看我网站上的 article I wrote on the subject

以下示例展示了如何在多个存储库中使用相同的上下文。为了简化,我没有使用接口,也没有使用容器注入依赖。

控制器class:

public class HomeController : Controller
{
    Context context;
    AdminRepository adminRepository;
    EntryRepository entryRepository;

    public HomeController()
    {
        context = new Context();
        adminRepository = new AdminRepository(context);
        entryRepository = new EntryRepository(context);
    }
    // GET: Home
    public ActionResult Index()
    {
        string login = "MyLogin";
        Admin admin = adminRepository.GetAdmin(login);
        Entry entry = new Entry() { Admin = admin};
        entryRepository.AddEntry(entry);
        return View(entry);
    }
}

存储库:

public class AdminRepository
{
    Context context;
    public AdminRepository(Context context)
    {
        this.context = context;

        // This seeds the database
        Admin admin = new Admin() { Login = "MyLogin" };
        this.context.Admins.Add(admin);
        this.context.SaveChanges();
    }

    public Admin GetAdmin(string login)
    {
        return context.Admins.Where(a => a.Login == login).FirstOrDefault();
    }
}

public class EntryRepository
{
    Context context;
    public EntryRepository(Context context)
    {
        this.context = context;
    }

    public void AddEntry(Entry entry){
        context.Entrys.Add(entry);
        context.SaveChanges();
    }
}

上下文 class:

public class Context : DbContext
{
    public Context()
    {
        Database.SetInitializer<Context>(new DropCreateDatabaseAlways<Context>());
        Database.Initialize(true);
    }

    public DbSet<Admin> Admins { get; set; }
    public DbSet<Entry> Entrys { get; set; }
}

改装型号:

public class Admin
{
    public int Id { get; set; }
    public string Login { get; set; }

}

public class Entry
{
    public int Id { get; set; }
    public virtual Admin Admin { get; set; }
}