Подтвердить что ты не робот

Шаблон хранилища с Entity Framework 4.1 и отношениями родителя/ребенка

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

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

У меня есть 3 таблицы в базе данных: Group, Person и GroupPersonMap. GroupPersonMap - таблица ссылок и состоит только из основных ключей Group и Person. Я создал EF-модель из трех таблиц с дизайнером 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 обновляется в базе данных каждый раз, когда я создаю карту Group/Person. Это связано с тем, что в моем методе EFRepository.Update() состояние объекта явно установлено на Modified'. I must set the Group state to Измененная таблица so the Group` не изменяется.

Чтобы исправить это, я должен добавить некоторый вид перегрузки 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 и шаблоном репозитория. Спасибо, что прочитали этот длинный пост

4b9b3361

Ответ 1

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

Нет не упростит ваше тестирование. Вы выставили IQueryable, чтобы ваш репозиторий не тестировался в блоке.

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

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

Но это не работает, потому что EF думает, что Человек новый, и попытается повторно ВСТАВИТЬ человека в базу данных, которая приведет к первичному ключу ошибка ограничения.

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

Теперь у меня проблема с тем, что в UPDATE'd добавлена ​​группа в базе данных каждый раз, когда я хочу создать карту Group/Person.

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

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

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