Шаблон репозитория с Entity Framework 4.1 и отношениями родитель / потомок - PullRequest
35 голосов
/ 24 августа 2011

У меня все еще есть некоторая путаница с шаблоном репозитория.Основная причина, по которой я хочу использовать этот шаблон, состоит в том, чтобы избегать вызова EF 4.1 определенных операций доступа к данным из домена.Я бы предпочел вызывать общие операции CRUD из интерфейса IRepository.Это облегчит тестирование, и если мне когда-нибудь потребуется изменить структуру доступа к данным, я смогу сделать это без рефакторинга большого количества кода.

Вот пример моей ситуации:

У меня есть 3 таблицы в базе данных: Group, Person и GroupPersonMap.GroupPersonMap является таблицей ссылок и состоит только из первичных ключей Group и Person.Я создал EF модель из 3 таблиц с VS 2010 дизайнером.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 уже существует в базе данных, поэтому просто создайте карту между Group и Person в таблице GroupPersonMap.

Таким образом, чтобы присоединить 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 Не изменено so the Таблица группы не изменяется.

Чтобы это исправить, я должен добавить что-то вроде Update перегрузка в мой IRepository, который не обновляет корневую сущность, или Group, в этом случае:

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

Внедрение EF4 метода Update будет выглядеть примерно так:

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 Ответ

66 голосов
/ 24 августа 2011

Основная причина, по которой я хочу использовать этот шаблон, состоит в том, чтобы избегать вызова EF 4.1 определенных операций доступа к данным из домена.Я бы предпочел вызывать общие операции CRUD из интерфейса IRepository.Это облегчит тестирование

Нет не облегчит тестирование . Вы выставили IQueryable, поэтому ваш репозиторий не может быть проверен на единицу .

Если мне когда-нибудь придется изменить структуру доступа к данным, я будубыть в состоянии сделать это без рефакторинга большого количества кода.

Нет, вам все равно придется много менять код, потому что вы выставили IQueryable и потому что EF / ORM - это утечка абстракции - ваш верхний уровеньожидает, что какое-то поведение происходит магически внутри вашего ORM (например, ленивая загрузка).Также это одна из самых странных причин, чтобы пойти в хранилище.Просто выберите правильную технологию сейчас и используйте ее, чтобы получить ставки на нее.Если вам придется изменить его позже, это означает, что либо вы допустили ошибку и выбрали неправильную, либо требования изменились - в любом случае это будет много работы.

Но это не работает, потому что EF думает, что Person новый, и попытается повторно вставить Person в базу данных, что приведет к ошибке ограничения первичного ключа.

Да, потому что вы используетеновый контекст для каждого хранилища = это неправильный подход.Репозитории должны иметь общий контекст.Ваше второе решение также неверно, потому что вы возвращаете свою EF-зависимость обратно в приложение - хранилище предоставляет контекст.Обычно это решается вторым шаблоном - единицей работы. Единица работы охватывает контекст , а единица работы образует набор атомарных изменений - SaveChanges должен быть выставлен на единицу работы для фиксации изменений, сделанных всеми связанными репозиториями.

Теперь у меня есть проблема с тем, что группа ОБНОВЛЯЕТСЯ в базе данных каждый раз, когда я хочу создать карту Группа / Персона.

Почему вы меняете состояние?Вы получили сущность из репозитория, поэтому до тех пор, пока вы не отсоедините ее, нет необходимости вызывать Attach и изменять состояние вручную.Все это должно происходить автоматически на присоединенном объекте.Просто позвоните SaveChanges.Если вы используете отдельные сущности, то вы должны правильно установить состояние для каждой сущности и отношения, поэтому в таком случае вам действительно потребуются некоторые логические или обновленные перегрузки для обработки всех сценариев.

Правильно ли я подхожу к этому?Мой репозиторий начинает выглядеть как EF, так как я начинаю работать с EF и шаблоном репозитория.

Я так не думаю.Прежде всего, вы не используете совокупные корни.Если вы это сделаете, вы сразу обнаружите, что универсальный репозиторий не подходит для этого.Репозиторий для агрегатных корней имеет специфические методы для каждого агрегатного корня для обработки работы с отношениями, агрегированными корнем.Group не является частью Person агрегата, но GroupPersonMap должно быть таким, чтобы в вашем хранилище Person были специальные методы для обработки добавления и удаления групп из лица (но не для создания или удаления самих групп).Универсальный репозиторий Imo - это избыточный слой .

...