具有实体框架 4.1 和父/子关系的存储库模式
Posted
技术标签:
【中文标题】具有实体框架 4.1 和父/子关系的存储库模式【英文标题】:Repository Pattern with Entity Framework 4.1 and Parent/Child Relationships 【发布时间】:2011-11-02 12:23:45 【问题描述】:我仍然对存储库模式有些困惑。我想使用此模式的主要原因是避免从域调用 EF 4.1 特定的数据访问操作。我宁愿从 IRepository 接口调用通用 CRUD 操作。这将使测试更容易,如果我将来必须更改数据访问框架,我将能够这样做而无需重构大量代码。
这是我的情况的一个例子:
我在数据库中有 3 个表:Group
、Person
和 GroupPersonMap
。 GroupPersonMap
是一个链接表,仅由 Group
和 Person
主键组成。我用 VS 2010 设计器创建了 3 个表的 EF 模型。 EF 足够聪明地假设GroupPersonMap
是一个链接表,因此它不会在设计器中显示它。我想使用我现有的域对象而不是 EF 生成的类,所以我关闭了模型的代码生成。
我现有的匹配EF模型的类如下:
public class Group
public int GroupId get; set;
public string Name get; set;
public virtual ICollection<Person> People get; set;
public class Person
public int PersonId get; set;
public string FirstName get; set;
public virtual ICollection<Group> Groups get; set;
我有一个像这样的通用存储库接口:
public interface IRepository<T> where T: class
IQueryable<T> GetAll();
T Add(T entity);
T Update(T entity);
void Delete(T entity);
void Save()
还有一个通用的 EF 存储库:
public class EF4Repository<T> : IRepository<T> where T: class
public DbContext Context get; private set;
private DbSet<T> _dbSet;
public EF4Repository(string connectionString)
Context = new DbContext(connectionString);
_dbSet = Context.Set<T>();
public EF4Repository(DbContext context)
Context = context;
_dbSet = Context.Set<T>();
public IQueryable<T> GetAll()
// code
public T Insert(T entity)
// code
public T Update(T entity)
Context.Entry(entity).State = System.Data.EntityState.Modified;
Context.SaveChanges();
public void Delete(T entity)
// code
public void Save()
// code
现在假设我只想将现有的Group
映射到现有的Person
。我将不得不执行以下操作:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>("name=connString");
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
group.People.Add(person);
groupRepository.Update(group);
但这不起作用,因为 EF 认为Person
是新的,并且会尝试将INSERT
重新Person
到数据库中,这将导致主键约束错误。我必须使用DbSet
的Attach
方法告诉EF Person
已经存在于数据库中,所以只需在GroupPersonMap
表中创建Group
和Person
之间的映射。
因此,为了将Person
附加到上下文中,我现在必须将Attach
方法添加到我的IRepository:
public interface IRepository<T> where T: class
// existing methods
T Attach(T entity);
修复主键约束错误:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context);
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
personRepository.Attach(person);
group.People.Add(person);
groupRepository.Update(group);
已修复。现在我必须处理另一个问题,每次创建组/人员映射时,Group
都会在数据库中被更新。这是因为在我的EFRepository.Update()
方法中,实体状态被显式设置为Modified'. I must set the Group's state to
Unchangedso the
Group` 表没有被修改。
要解决这个问题,我必须在不更新根实体的 IRepository 或 Group
中添加某种 Update
重载,在这种情况下:
public interface IRepository<T> where T: class
// existing methods
T Update(T entity, bool updateRootEntity);
Update 方法的 EF4 实现如下所示:
T Update(T entity, bool updateRootEntity)
if (updateRootEntity)
Context.Entry(entity).State = System.Data.EntityState.Modified;
else
Context.Entry(entity).State = System.Data.EntityState.Unchanged;
Context.SaveChanges();
我的问题是:我是否以正确的方式处理这个问题?当我开始使用 EF 和存储库模式时,我的存储库开始看起来以 EF 为中心。感谢您阅读这篇长文
【问题讨论】:
【参考方案1】:我想使用这种模式的主要原因是避免调用 来自域的 EF 4.1 特定数据访问操作。我宁愿 从 IRepository 接口调用通用 CRUD 操作。这会 让测试更容易
不,will not make your testing easier。 You exposed IQueryable
所以你的存储库is not unit testable。
如果我将来必须更改数据访问框架,我 无需重构大量代码就能做到这一点。
不,您无论如何都必须更改很多代码,因为您暴露了IQueryable
,并且因为 EF / ORM 是泄漏抽象 - 您的上层期望某些行为在您的 ORM 内部神奇地发生(例如延迟加载)。这也是使用存储库的最奇怪的原因之一。现在只需选择正确的技术并使用它来获得它的赌注。如果您稍后必须更改它,这意味着you did a mistake and chose the wrong one or requirements have changed - 在任何一种情况下都需要大量工作。
但这不起作用,因为 EF 认为 Person 是新的,并且会尝试 将 Person 重新插入到数据库中,这将导致主键 约束错误。
是的,因为您为每个存储库都使用了新的上下文 = 这是错误的方法。存储库必须共享上下文。您的第二个解决方案也不正确,因为您将 EF 依赖项放回应用程序 - 存储库正在公开上下文。这通常通过第二种模式 - 工作单元来解决。 Unit of work wraps the context 和工作单元构成原子更改集 - SaveChanges
必须在工作单元上公开才能提交所有相关存储库所做的更改。
现在我遇到了数据库中正在更新的组的问题 每次我想创建一个组/个人地图。
为什么要改变状态?您从存储库收到实体,因此在您分离它之前,没有理由调用 Attach
并手动更改状态。这一切都应该在附加实体上自动发生。只需致电SaveChanges
。如果您使用的是分离实体,那么 you must correctly set state for every entity 和关系,所以在这种情况下,您确实需要一些逻辑或更新重载来处理所有场景。
我是否以正确的方式处理这个问题?我的存储库开始查找 当我开始使用 EF 和存储库模式时,以 EF 为中心。
我不这么认为。首先,您没有使用聚合根。如果你这样做了,你会立即发现generic repository 不适合那个。聚合根的存储库具有每个聚合根的特定方法来处理由根聚合的关系。 Group
不是 Person
聚合的一部分,但 GroupPersonMap
应该是这样,因此您的 Person 存储库应该有特定的方法来处理从 person 添加和删除组(但不是创建或删除组本身)。 Imo 通用存储库是 redundant layer。
【讨论】:
+1 表示实体框架 + 工作单元。存储库需要共享相同的 dbcontext。我敢打赌这就是我的问题。以上是关于具有实体框架 4.1 和父/子关系的存储库模式的主要内容,如果未能解决你的问题,请参考以下文章
如何在具有存储库模式的实体框架中伪造 DbContext.Entry 方法