如何处理通用存储库、工作单元模式中的多对多关系?
How to deal with many-to-many relationships in the general repository, unit of work pattern?
为了我的论文,我决定在 MVC 中创建一些东西并挑战自己,我添加了一个 DAL 和 BL 层。我在 BL 中创建了 "services",允许我使用我的实体。
我真的很想知道我是否正确理解了该模式,因为我在处理多对多关系时遇到了问题 - 尤其是如何正确使用它们。
这是我当前的实现(经过简化,以获得总体思路):
PersonService:这个class是我使用我的实体的抽象(我也有几个实体工厂)。每当我需要将一个人添加到我的数据库时,我都会使用我的服务。我只是注意到 mPersonRepository 可能应该以不同的方式命名。
public class PersonService : IService<Person> {
private UnitOfWork mPersonRepository;
public PersonService() => mPersonRepository = new UnitOfWork();
public void Add(Person aPerson) {
mPersonRepository.PersonRepository.Insert(aPerson);
mPersonRepository.Safe();
}
public void Delete(Guid aGuid) {
mPersonRepository.PersonRepository.Delete(aGuid);
mPersonRepository.Safe();
}
public Person Find(Expression<Func<Person, bool>> aFilter = null) {
var lPerson = mPersonRepository.PersonRepository.Get(aFilter).FirstOrDefault();
return lPerson;
}
public void Update(Person aPerson) {
mPersonRepository.PersonRepository.Update(aPerson);
mPersonRepository.Safe();
}
}
public interface IService<TEntity> where TEntity : class {
void Add(TEntity aEntity);
void Update(TEntity aEntity);
void Delete(Guid aGuid);
TEntity Find(Expression<Func<TEntity, bool>> aExpression);
TEntity FindByOid(Guid aGuid);
IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> aExpression);
int Count();
}
UnitOfWork:与 Microsoft 实现它的方式非常相似。
public class UnitOfWork : IUnitOfWork {
private readonly DbContextOptions<PMDContext> mDbContextOptions = new DbContextOptions<PMDContext>();
public PMDContext mContext;
public UnitOfWork() => mContext = new PMDContext(mDbContextOptions);
public void Safe() => mContext.SaveChanges();
private bool mDisposed = false;
protected virtual void Dispose(bool aDisposed) {
if (!mDisposed)
if (aDisposed) mContext.Dispose();
mDisposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private GenericRepository<Person> mPersonRepository;
private GenericRepository<Project> mProjectRepository;
public GenericRepository<Person> PersonRepository => mPersonRepository ?? new GenericRepository<Person>(mContext);
public GenericRepository<Project> ProjectRepository => mProjectRepository ?? new GenericRepository<Project>(mContext);
GenericRepository: 和以前一样,很像。
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class {
internal PMDContext mContext;
internal DbSet<TEntity> mDbSet;
public GenericRepository(PMDContext aContext) {
mContext = aContext;
mDbSet = aContext.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> aFilter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> aOrderBy = null,
string aProperties = "") {
var lQuery = (IQueryable<TEntity>)mDbSet;
if (aFilter != null) lQuery = lQuery.Where(aFilter);
foreach (var lProperty in aProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
lQuery = lQuery.Include(lProperty);
}
return aOrderBy != null ? aOrderBy(lQuery).ToList() : lQuery.ToList();
}
public virtual TEntity GetById(object aId) => mDbSet.Find(aId);
public virtual void Insert(TEntity aEntity) => mDbSet.Add(aEntity);
public virtual void Delete(object aId) {
var lEntity = mDbSet.Find(aId);
Delete(lEntity);
}
public virtual void Delete(TEntity aEntity) {
if (mContext.Entry(aEntity).State == EntityState.Detached) mDbSet.Attach(aEntity);
mDbSet.Remove(aEntity);
}
public virtual void Update(TEntity aEntity) {
mDbSet.Attach(aEntity);
mContext.Entry(aEntity).State = EntityState.Modified;
}
}
PMDContext: DbContext 的实现。
public class PMDContext : DbContext {
public PMDContext(DbContextOptions<PMDContext> aOptions) : base(aOptions) { }
public DbSet<Person> Persons { get; set; }
public DbSet<Project> Projects { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder aOptions) {
if (!aOptions.IsConfigured) aOptions.UseSqlServer("<snip>");
}
}
实体
public class Person {
public Person(<args>) {}
public Guid Oid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Project {
public Project(<args>) {}
public Guid Oid { get; set; }
public string Name { get; set; }
}
我都是这样使用的:
var lPerson = Factory.CreatePerson(<args>);
var lPersonService = new PersonService();
lPersonService.Add(lPerson);
<..do some work..>
lPersonService.Update(lPerson)
现在我不需要担心调用 Safe 或其他任何东西。它工作得很好,但现在我 运行 遇到了一个问题:我如何处理我的实体中的多对多关系。例如,我的人可以有多个项目,我的项目可以有多个人。
我更新了 PMDContext 以获得 link table:
protected override void OnModelCreating(ModelBuilder aModelBuilder) {
aModelBuilder.Entity<PersonProject>().HasKey(x => new { x.PersonOid, x.ProjectOid });
}
Link table
public class PersonProject {
public Guid PersonOid { get; set; }
public Guid ProjectOid { get; set; }
}
并使用以下 属性.
更新了我的两个实体
public ICollection<PersonProject> PersonProjects { get; } = new List<PersonProject>();
现在我对如何使用我的 linked table 感到困惑。我想我可以采用类似的方法:
var lPerson = PersonService.FindByOid(aPersonOid);
var lProject = ProjectService.FindByOid(aProjectOid);
var lPersonProject = new PersonProject() { PersonOid = aPersonOid,
ProjectOid = aProjectOid };
lPerson.PersonProjects.Add(lPersonProject);
lProject.PersonProjects.Add(lPersonProject);
PersonService.Update(lPerson);
ProjectService.Update(lProject);
但这最终没有对我数据库中的 PersonProject table 做任何事情。我的猜测是我缺少实际写入 table 的代码,因为我没有处理此问题的 PersonProject 服务。我很迷惑。
我将如何使用我当前的方法前进,或者我必须改变什么?我只是一个使用实体框架的初学者,很高兴我能走到这一步。
特别是在服务 -> 模式实施方面的任何输入都将受到赞赏。我一定是做错了什么。
谢谢!
您并没有真正使用服务层模式。您的 "service" 只是一个存储库,然后它使用您的工作单元访问另一个存储库。简而言之,你在这里有多层无意义的抽象,这绝对会 杀死 你在一个你必须维护任何时间的应用程序中。
一般来说,您应该不 将工作单元/存储库模式与 Entity Framework 这样的 ORM 一起使用。原因很简单:这些 ORM 已经 实现了这些模式。对于 EF,DbContext
是您的工作单元,每个 DbSet
是一个存储库。
如果您要使用 Entity Framework 之类的东西,我最好的建议是 直接使用它 。在你的应用程序中引用它,将你的上下文注入你的控制器等等,然后实际使用 EF API 来做你需要做的事情。这不是在创建紧耦合吗?是的。是的。然而,很多人(甚至我自己也错过了很长时间)的要点是耦合 已经 了。即使你抽象了一切,你仍然在处理一个你永远无法完全抽象的特定领域。如果您更改数据库,那么 将 在某个时候冒泡到您的应用程序,即使您更改的是 DTO 而不是实体。而且,当然,您仍然需要更改这些实体。这些层只会为您的应用程序增加更多的维护和熵,这实际上是 "clean code" 架构抽象的对立面。
但是如果您需要用其他东西关闭 EF 怎么办?你不会需要重写一堆代码吗?嗯,是的。然而,这几乎永远不会发生。在诸如 ORM 之类的东西上做出选择具有足够的动力,以至于无论您做什么,无论您使用多少层抽象,您都不可能扭转这一进程。它只会需要太多的时间和精力,而且永远不会成为业务优先事项。而且,重要的是,无论如何都必须重写一堆代码。这只是在哪一层完成的问题。
现在,综上所述,CQRS(命令查询责任分离)等某些模式是有价值的,它是一种抽象(而不是无意义的)。但是,这仅在大型项目或领域中才有意义,在这些项目或领域中,您需要明确区分读取和写入 and/or 事件源(这与 CQRS 很自然)。对于大多数应用程序来说,这太过分了。
如果您想从主应用程序中抽象出 EF,我最推荐的是实际创建微服务。这些微服务基本上只是很小的 API(尽管它们 没有 ),它们只处理应用程序的单个功能单元。然后,您的应用程序发出请求或以其他方式访问微服务以获取所需的数据。微服务将直接使用 EF,而应用程序将完全不依赖 EF(圣杯开发人员认为他们想要)。
使用微服务架构,您实际上可以勾选您认为这种虚假抽象让您满意的所有方框。想用其他东西关闭 EF?没问题。由于每个微服务仅适用于域的有限子集,因此通常不会有大量代码。即使直接使用 EF,重写这些部分也相对简单。更好的是,每个微服务都是完全独立的,因此您可以在一个微服务上关闭 EF,但在另一个微服务上继续使用 EF。一切都在继续工作,应用程序一点也不在乎。这使您能够以可管理的速度随着时间的推移处理迁移。
长短不一over-engineer。这甚至是已经在这个行业工作了一段时间的开发人员的祸根,尤其是新开发人员,他们刚走出大门,脑海中就浮现出代码模式的愿景。请记住,这些模式是 推荐的 解决 特定 问题的方法。首先,您需要确保您确实遇到了问题,然后您需要关注该模式是否实际上是解决该问题的最佳方法您的具体情况。这是一种技能——你会随着时间的推移学会的。实现目标的最佳方式是从小处着手。以尽可能 straight-forward 的方式构建最低限度的功能。然后,重构。测试、配置文件,将其扔给狼群并拖回 blood-soaked 遗骸。然后,重构。最终,您可能会实现各种不同的层和模式,但也可能不会。重要的是那些 "might not" 次,因为在那些情况下,您拥有简单、易于维护的代码,这些代码可以正常工作,并且不会浪费大量开发时间。
为了我的论文,我决定在 MVC 中创建一些东西并挑战自己,我添加了一个 DAL 和 BL 层。我在 BL 中创建了 "services",允许我使用我的实体。
我真的很想知道我是否正确理解了该模式,因为我在处理多对多关系时遇到了问题 - 尤其是如何正确使用它们。
这是我当前的实现(经过简化,以获得总体思路):
PersonService:这个class是我使用我的实体的抽象(我也有几个实体工厂)。每当我需要将一个人添加到我的数据库时,我都会使用我的服务。我只是注意到 mPersonRepository 可能应该以不同的方式命名。
public class PersonService : IService<Person> {
private UnitOfWork mPersonRepository;
public PersonService() => mPersonRepository = new UnitOfWork();
public void Add(Person aPerson) {
mPersonRepository.PersonRepository.Insert(aPerson);
mPersonRepository.Safe();
}
public void Delete(Guid aGuid) {
mPersonRepository.PersonRepository.Delete(aGuid);
mPersonRepository.Safe();
}
public Person Find(Expression<Func<Person, bool>> aFilter = null) {
var lPerson = mPersonRepository.PersonRepository.Get(aFilter).FirstOrDefault();
return lPerson;
}
public void Update(Person aPerson) {
mPersonRepository.PersonRepository.Update(aPerson);
mPersonRepository.Safe();
}
}
public interface IService<TEntity> where TEntity : class {
void Add(TEntity aEntity);
void Update(TEntity aEntity);
void Delete(Guid aGuid);
TEntity Find(Expression<Func<TEntity, bool>> aExpression);
TEntity FindByOid(Guid aGuid);
IEnumerable<TEntity> FindAll(Expression<Func<TEntity, bool>> aExpression);
int Count();
}
UnitOfWork:与 Microsoft 实现它的方式非常相似。
public class UnitOfWork : IUnitOfWork {
private readonly DbContextOptions<PMDContext> mDbContextOptions = new DbContextOptions<PMDContext>();
public PMDContext mContext;
public UnitOfWork() => mContext = new PMDContext(mDbContextOptions);
public void Safe() => mContext.SaveChanges();
private bool mDisposed = false;
protected virtual void Dispose(bool aDisposed) {
if (!mDisposed)
if (aDisposed) mContext.Dispose();
mDisposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
private GenericRepository<Person> mPersonRepository;
private GenericRepository<Project> mProjectRepository;
public GenericRepository<Person> PersonRepository => mPersonRepository ?? new GenericRepository<Person>(mContext);
public GenericRepository<Project> ProjectRepository => mProjectRepository ?? new GenericRepository<Project>(mContext);
GenericRepository: 和以前一样,很像。
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class {
internal PMDContext mContext;
internal DbSet<TEntity> mDbSet;
public GenericRepository(PMDContext aContext) {
mContext = aContext;
mDbSet = aContext.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> aFilter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> aOrderBy = null,
string aProperties = "") {
var lQuery = (IQueryable<TEntity>)mDbSet;
if (aFilter != null) lQuery = lQuery.Where(aFilter);
foreach (var lProperty in aProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
lQuery = lQuery.Include(lProperty);
}
return aOrderBy != null ? aOrderBy(lQuery).ToList() : lQuery.ToList();
}
public virtual TEntity GetById(object aId) => mDbSet.Find(aId);
public virtual void Insert(TEntity aEntity) => mDbSet.Add(aEntity);
public virtual void Delete(object aId) {
var lEntity = mDbSet.Find(aId);
Delete(lEntity);
}
public virtual void Delete(TEntity aEntity) {
if (mContext.Entry(aEntity).State == EntityState.Detached) mDbSet.Attach(aEntity);
mDbSet.Remove(aEntity);
}
public virtual void Update(TEntity aEntity) {
mDbSet.Attach(aEntity);
mContext.Entry(aEntity).State = EntityState.Modified;
}
}
PMDContext: DbContext 的实现。
public class PMDContext : DbContext {
public PMDContext(DbContextOptions<PMDContext> aOptions) : base(aOptions) { }
public DbSet<Person> Persons { get; set; }
public DbSet<Project> Projects { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder aOptions) {
if (!aOptions.IsConfigured) aOptions.UseSqlServer("<snip>");
}
}
实体
public class Person {
public Person(<args>) {}
public Guid Oid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Project {
public Project(<args>) {}
public Guid Oid { get; set; }
public string Name { get; set; }
}
我都是这样使用的:
var lPerson = Factory.CreatePerson(<args>);
var lPersonService = new PersonService();
lPersonService.Add(lPerson);
<..do some work..>
lPersonService.Update(lPerson)
现在我不需要担心调用 Safe 或其他任何东西。它工作得很好,但现在我 运行 遇到了一个问题:我如何处理我的实体中的多对多关系。例如,我的人可以有多个项目,我的项目可以有多个人。
我更新了 PMDContext 以获得 link table:
protected override void OnModelCreating(ModelBuilder aModelBuilder) {
aModelBuilder.Entity<PersonProject>().HasKey(x => new { x.PersonOid, x.ProjectOid });
}
Link table
public class PersonProject {
public Guid PersonOid { get; set; }
public Guid ProjectOid { get; set; }
}
并使用以下 属性.
更新了我的两个实体public ICollection<PersonProject> PersonProjects { get; } = new List<PersonProject>();
现在我对如何使用我的 linked table 感到困惑。我想我可以采用类似的方法:
var lPerson = PersonService.FindByOid(aPersonOid);
var lProject = ProjectService.FindByOid(aProjectOid);
var lPersonProject = new PersonProject() { PersonOid = aPersonOid,
ProjectOid = aProjectOid };
lPerson.PersonProjects.Add(lPersonProject);
lProject.PersonProjects.Add(lPersonProject);
PersonService.Update(lPerson);
ProjectService.Update(lProject);
但这最终没有对我数据库中的 PersonProject table 做任何事情。我的猜测是我缺少实际写入 table 的代码,因为我没有处理此问题的 PersonProject 服务。我很迷惑。
我将如何使用我当前的方法前进,或者我必须改变什么?我只是一个使用实体框架的初学者,很高兴我能走到这一步。
特别是在服务 -> 模式实施方面的任何输入都将受到赞赏。我一定是做错了什么。
谢谢!
您并没有真正使用服务层模式。您的 "service" 只是一个存储库,然后它使用您的工作单元访问另一个存储库。简而言之,你在这里有多层无意义的抽象,这绝对会 杀死 你在一个你必须维护任何时间的应用程序中。
一般来说,您应该不 将工作单元/存储库模式与 Entity Framework 这样的 ORM 一起使用。原因很简单:这些 ORM 已经 实现了这些模式。对于 EF,DbContext
是您的工作单元,每个 DbSet
是一个存储库。
如果您要使用 Entity Framework 之类的东西,我最好的建议是 直接使用它 。在你的应用程序中引用它,将你的上下文注入你的控制器等等,然后实际使用 EF API 来做你需要做的事情。这不是在创建紧耦合吗?是的。是的。然而,很多人(甚至我自己也错过了很长时间)的要点是耦合 已经 了。即使你抽象了一切,你仍然在处理一个你永远无法完全抽象的特定领域。如果您更改数据库,那么 将 在某个时候冒泡到您的应用程序,即使您更改的是 DTO 而不是实体。而且,当然,您仍然需要更改这些实体。这些层只会为您的应用程序增加更多的维护和熵,这实际上是 "clean code" 架构抽象的对立面。
但是如果您需要用其他东西关闭 EF 怎么办?你不会需要重写一堆代码吗?嗯,是的。然而,这几乎永远不会发生。在诸如 ORM 之类的东西上做出选择具有足够的动力,以至于无论您做什么,无论您使用多少层抽象,您都不可能扭转这一进程。它只会需要太多的时间和精力,而且永远不会成为业务优先事项。而且,重要的是,无论如何都必须重写一堆代码。这只是在哪一层完成的问题。
现在,综上所述,CQRS(命令查询责任分离)等某些模式是有价值的,它是一种抽象(而不是无意义的)。但是,这仅在大型项目或领域中才有意义,在这些项目或领域中,您需要明确区分读取和写入 and/or 事件源(这与 CQRS 很自然)。对于大多数应用程序来说,这太过分了。
如果您想从主应用程序中抽象出 EF,我最推荐的是实际创建微服务。这些微服务基本上只是很小的 API(尽管它们 没有 ),它们只处理应用程序的单个功能单元。然后,您的应用程序发出请求或以其他方式访问微服务以获取所需的数据。微服务将直接使用 EF,而应用程序将完全不依赖 EF(圣杯开发人员认为他们想要)。
使用微服务架构,您实际上可以勾选您认为这种虚假抽象让您满意的所有方框。想用其他东西关闭 EF?没问题。由于每个微服务仅适用于域的有限子集,因此通常不会有大量代码。即使直接使用 EF,重写这些部分也相对简单。更好的是,每个微服务都是完全独立的,因此您可以在一个微服务上关闭 EF,但在另一个微服务上继续使用 EF。一切都在继续工作,应用程序一点也不在乎。这使您能够以可管理的速度随着时间的推移处理迁移。
长短不一over-engineer。这甚至是已经在这个行业工作了一段时间的开发人员的祸根,尤其是新开发人员,他们刚走出大门,脑海中就浮现出代码模式的愿景。请记住,这些模式是 推荐的 解决 特定 问题的方法。首先,您需要确保您确实遇到了问题,然后您需要关注该模式是否实际上是解决该问题的最佳方法您的具体情况。这是一种技能——你会随着时间的推移学会的。实现目标的最佳方式是从小处着手。以尽可能 straight-forward 的方式构建最低限度的功能。然后,重构。测试、配置文件,将其扔给狼群并拖回 blood-soaked 遗骸。然后,重构。最终,您可能会实现各种不同的层和模式,但也可能不会。重要的是那些 "might not" 次,因为在那些情况下,您拥有简单、易于维护的代码,这些代码可以正常工作,并且不会浪费大量开发时间。