使用存储库模式时的多个数据库上下文
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",这是每个存储库中有单独的数据库上下文的结果。
当我在寻找解决方案时,我已经知道解决它的最佳方法可能是使用一个公共上下文。然后我发现了工作单元模式。但真正的问题开始了。
- 在许多站点上,对此的解决方案略有不同。
- 存储库必须有通用的通用接口(我不想使用它,因为我不需要对每个实体进行每个 CRUD 操作,而且有时我需要有额外的方法,例如 "IfExists", 等等)
- 在一些网站上,我读到不需要整个抽象,因为 Entity Framework 已经提供了抽象并且 UoW 在 DbContext 中实现(无论那意味着什么)
- 工作单元模式(至少从互联网上的例子来看)对我来说似乎是一个真正的痛苦...
我需要一些指导...我只学习 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
这样的接口,然后创建像 EntityFrameworkRepository
、NHibernateRepository
、WebApiRepository
等这样的实现。您的应用程序将永远只依赖于 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; }
}
我现在有点迷茫...我从未见过关于解决问题的如此多的不同信息。但让我们从头开始。
由于 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",这是每个存储库中有单独的数据库上下文的结果。
当我在寻找解决方案时,我已经知道解决它的最佳方法可能是使用一个公共上下文。然后我发现了工作单元模式。但真正的问题开始了。
- 在许多站点上,对此的解决方案略有不同。
- 存储库必须有通用的通用接口(我不想使用它,因为我不需要对每个实体进行每个 CRUD 操作,而且有时我需要有额外的方法,例如 "IfExists", 等等)
- 在一些网站上,我读到不需要整个抽象,因为 Entity Framework 已经提供了抽象并且 UoW 在 DbContext 中实现(无论那意味着什么)
- 工作单元模式(至少从互联网上的例子来看)对我来说似乎是一个真正的痛苦...
我需要一些指导...我只学习 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
这样的接口,然后创建像 EntityFrameworkRepository
、NHibernateRepository
、WebApiRepository
等这样的实现。您的应用程序将永远只依赖于 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; }
}