实体框架4.1和父子关系的存储库模式 [英] Repository Pattern with Entity Framework 4.1 and Parent/Child Relationships

查看:92
本文介绍了实体框架4.1和父子关系的存储库模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我仍然对存储库模式感到困惑.我要使用此模式的主要原因是避免从域调用EF 4.1特定的数据访问操作.我宁愿从IRepository接口调用通用CRUD操作.这将使测试变得更加容易,并且如果将来将来我不得不更改数据访问框架,我将能够这样做而无需重构大量代码.

I still have some confusion with the Repository Pattern. The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier and if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.

这是我的情况的一个例子:

Here is an example of my situation:

我在数据库中有3个表:GroupPersonGroupPersonMap. GroupPersonMap是一个链接表,仅由GroupPerson主键组成.我使用VS 2010设计器创建了3个表的EF模型. EF足够聪明,可以假定GroupPersonMap是链接表,因此它不会在设计器中显示.我想使用现有的域对象而不是EF的生成类,因此我关闭了该模型的代码生成.

I have 3 tables in the database: Group, Person, and GroupPersonMap. GroupPersonMap is a link table and just consists of the Group and Person primary keys. I created an EF model of the 3 tables with VS 2010 designer. EF was smart enough to assume GroupPersonMap is a link table so it doesn't show it in the designer. I want to use my existing domain objects instead of EF's generated classes so I turn off code generation for the model.

我与EF模型匹配的现有类如下:

My existing classes that matches the EF model are as follows:

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; }
}

我有一个通用的存储库接口,如下所示:

I have a generic repository interface like so:

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.我将必须执行以下操作:

Now suppose I just want to map an existing Group to an existing Person. I would have to do something like the following:

        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是新的,并且会尝试将Person重新INSERT到数据库中,这将导致主键约束错误.我必须使用DbSetAttach方法来告诉EF数据库中已经存在Person,因此只需在GroupPersonMap表中的GroupPerson之间创建一个映射.

But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error. I must use DbSet's Attach method to tell EF that the Person already exists in the database so just create a map between Group and Person in the GroupPersonMap table.

因此,为了将Person附加到上下文,我现在必须在我的IRepository中添加一个Attach方法:

So in order to attach Person to the context I must now add an Attach method to my 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 Unchanged so the Group`表不会被修改.

Fixed. Now I have to deal with another issue where Group is being UPDATE'd in the database every time I create a Group/Person map. This is because in my EFRepository.Update() method, the entity state is explicitly set to Modified'. I must set the Group's state toUnchangedso theGroup` table doesn't get modified.

要解决此问题,我必须在我的IRepository中添加某种Update重载,在这种情况下,该重载不会更新根实体或Group:

To fix this I must add some sort of Update overload to my IRepository that does not update the root entity, or Group, in this case:

public interface IRepository<T> where T: class
{
    // existing methods
    T Update(T entity, bool updateRootEntity);
}

Update方法的EF4配置看起来像这样:

The EF4 implentation of the Update method would look something like this:

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为中心.感谢您阅读这篇长文章

My question is: Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern. Thanks for reading this long post

推荐答案

我要使用此模式的主要原因是避免调用 来自域的EF 4.1特定数据访问操作.我宁愿 从IRepository接口调用通用CRUD操作.这将 使测试更容易

The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier

不会使您的测试更容易. 您暴露了 ,因此您的存储库不是可测试的单元.

No it will not make your testing easier. You exposed IQueryable so your repository is not unit testable.

如果将来我不得不更改数据访问框架,我 无需重构大量代码就可以做到这一点.

if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.

否,因为您暴露了IQueryable并且因为EF/ORM是泄漏抽象,所以您无论如何都不必更改很多代码-您的上层希望ORM内部发生某些魔术般的行为(例如,延迟加载).这也是选择存储库的最奇怪的原因之一.现在就选择正确的技术,并使用它来赢得赌注.如果您以后要更改它,则意味着您犯了一个错误并选择了错误的要求或要求已更改-在任何一种情况下,这都会需要大量的工作.

No you will have to change a lot of code anyway because you exposed IQueryable and because EF / ORM is leaky abstraction - your upper layer expects some behavior happens magically inside your ORM (for example lazy loading). Also this is one of the most odd reasons to go for repository. Simply choose the right technology now and use it to get the bets of it. If you have to change it later it means either that you did a mistake and chose the wrong one or requirements have changed - in either case it will be a lot of work.

但是这不起作用,因为EF认为Person是新手,并将尝试 将Person重新插入数据库,这将导致主键 约束错误.

But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error.

是的,因为您正在为每个存储库使用新的上下文=这是错误的方法.存储库必须共享上下文.第二种解决方案也不正确,因为您将EF依赖关系放回了应用程序-存储库正在公开上下文.这通常可以通过第二种模式-工作单元来解决. 工作单元包装了上下文和工作单元形成原子更改集-SaveChanges必须在工作单元中公开,以提交所有相关存储库所做的更改.

Yes because you are using a new context for each repository = that is wrong approach. Repositories must share the context. Your second solution is not correct as well because you put your EF dependency back to the application - repository is exposing the context. This is usually solved by second pattern - unit of work. Unit of work wraps the context and unit of work forms the atomic change set - SaveChanges must be exposed on unit of work to commit changes done by all related repositories.

现在我在数据库中更新组时遇到了问题 每次我想创建小组/人员地图时.

Now I have an issue with the Group being UPDATE'd in the database every time I want to create a Group/Person map.

为什么要更改状态?您是从存储库中收到实体的,因此在将其分离之前,没有理由调用Attach并手动更改状态.所有这些都应该在附加实体上自动发生.只需调用SaveChanges.如果您使用的是分离实体,则您必须正确为每个实体和关系设置状态,因此在这种情况下,您确实需要一些逻辑或更新重载来处理所有情况.

Why do you change the state? You received entity from the repository so until you detached it there is no reason to call Attach and change the state manually. This all should happen automatically on attached entity. Simply call SaveChanges. If you are using detached entities then you must correctly set state for every entity and relation so in such case you will indeed needs some logic or update overloads to handle all scenarios.

我采用正确的方法吗?我的存储库开始看起来 当我开始使用EF和存储库模式时,以EF为中心.

Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern.

我不这么认为.首先,您没有使用聚合根.如果这样做,您会立即发现通用存储库不适合这样做.聚合根的存储库对每个聚合根都有特定的方法来处理由根聚合的关系. Group不是Person聚合的一部分,但GroupPersonMap应该是这样,因此您的Person存储库应具有特定的方法来处理从person中添加和删除组(而不是自己创建或删除组). Imo通用存储库为冗余层.

I don't think so. First of all you are not using aggregate roots. If you do you would immediately found that generic repository is not suitable for that. Repository for aggregate roots have specific methods per aggregate root to handle working with relations aggregated by the root. Group is not part of Person aggregate but GroupPersonMap should be so your Person repository should have specific methods to handle adding and removing groups from person (but not to create or delete groups themselves). Imo generic repository is redundant layer.

这篇关于实体框架4.1和父子关系的存储库模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆