Re: [Entity Framework] Update EntityCollection
От: sto Украина http://overstore.codeplex.com
Дата: 04.03.10 21:18
Оценка: 2 (1)
Здравствуйте, NoHate, Вы писали:

NH>Можно воспользоваться этим путем. Но в таком случае нужно разделить все операции на три категории: add, modify, delete, а затем пробежать по каждой категории и сделать для каждого элемента соответствующий запрос.

NH>Но тогда возникает другая проблема: кто-то должен генерировать эти списки операций. Тут варианта два: либо записывать действия на стороне клиента, либо определять изменения на стороне сервера.
NH>• Если на стороне клиента, то нужно посылать несколько списков (3 вместо одного). Это сильно усложнит реализацию GUI, по причине того, что нужно будет постоянно следать за всеми действиями пользователя + достаточно сильно усложнится модель.
NH>• Если на стороне сервера, то нужно будет сделать еще один запрос в БД, чтобы выяснить, какие записи нужно удалить, какие добавить, какие обновить.

NH>Все это видится мне какими-то велосипедами. Ведь EF сам умеет правильно добавлять реляции, умеет их удалять, при удалении родительского элемента. А апдейта разве нет?


NH>Буду очень рад, если кто-нибудь укажет, что почитать, какой проект/кусок кода посмотреть или просто, в какую сторону мне двигаться.

NH>Весь инет завален банальными примерами: GetEntity/UpdateEntity, а вот что-то приближенное к реальности найти тяжело.

NH>Сейчас перечитал – вроде бы доступно написал.


В принципе, все вы правильно написали. Либо запрашивать коллекцию ContactInfo и удалять явно отсутствующие контакты, либо завести специальный класс коллекции, который будет хранить в отдельном свойстве удаленные записи, и во всех контрактах использовать его. Не знаю, насколько удастся подружить это с Сильверлайтом.

Мне кажется, что дополнительный запрос к базе данных не сильно скажется на производительности, по сравнению с кучей UPDATE/INSERT, которые последуют за ним.
Хуже, если у вас есть оптимистическая блокировка в таблице для ContactInfo. В случае перезапроса данных с сервера и аттача возможно поле версии будет заменено более новым.
Но тут надо проверять.

Если ContactInfo не является частью связи один-ко-многим со стороны "один" (никакие другие таблицы не содержат ссылку на таблицу для ContactInfo), то возможно вам подойдет перед сохранением записи удалять, а потом только добавлять.
There is no such thing as the perfect design.
[Entity Framework] Update EntityCollection
От: NoHate  
Дата: 04.03.10 18:52
Оценка:
Добрый день.

Разбираюсь с Entity Framework 4 (EF 1 не использовал). Не могу найти в инете способо разрешения классической проблемы.

Имеется две entity:

Customer
int Id
string Name


ContactInfo
int Id
int IdCustomer
string Value


Отношения Customer-ContactInfo: 1-n.

Имеется бизнес-слой и GUI на Silverlight/WPF. В окне редактирования Customer-а находится datagrid, к которому прибинден список ContactInfo.
Пользователь производит со списком ContactInfo какие-либо действия (add/modify/delete), а потом сохраняет изменения на сервер.
Вопрос в том, как сохранить эти изменения.


Я не видел решения этой проблемы, поэтому просто предложу варианты:

В идеале хотелось бы сделать как-то так:

void UpdateCustomer(int customerId, List<ContactInfo> contactInfoList) 
{
      Customer customer = new Customer();
      customer.Id = customerId
      myEntityContext.Customers.Attach(customer);
 
      customer.ContactInfos.Attach(contactInfoList);
      myEntityContext.SaveChanges();
}

Но это не работает: attach для нескольких Entity у меня только добавляет, но не удаляет.

Я смог модифицировать реляции только прямым вызовом:

Customer.ContactInfos.Add(…);
Customer.ContactInfos.Remove(…);

Можно воспользоваться этим путем. Но в таком случае нужно разделить все операции на три категории: add, modify, delete, а затем пробежать по каждой категории и сделать для каждого элемента соответствующий запрос.
Но тогда возникает другая проблема: кто-то должен генерировать эти списки операций. Тут варианта два: либо записывать действия на стороне клиента, либо определять изменения на стороне сервера.
• Если на стороне клиента, то нужно посылать несколько списков (3 вместо одного). Это сильно усложнит реализацию GUI, по причине того, что нужно будет постоянно следать за всеми действиями пользователя + достаточно сильно усложнится модель.
• Если на стороне сервера, то нужно будет сделать еще один запрос в БД, чтобы выяснить, какие записи нужно удалить, какие добавить, какие обновить.

Все это видится мне какими-то велосипедами. Ведь EF сам умеет правильно добавлять реляции, умеет их удалять, при удалении родительского элемента. А апдейта разве нет?

Буду очень рад, если кто-нибудь укажет, что почитать, какой проект/кусок кода посмотреть или просто, в какую сторону мне двигаться.
Весь инет завален банальными примерами: GetEntity/UpdateEntity, а вот что-то приближенное к реальности найти тяжело.

Сейчас перечитал – вроде бы доступно написал.
Re[2]: [Entity Framework] Update EntityCollection
От: NoHate  
Дата: 04.03.10 22:15
Оценка:
Здравствуйте, sto, Вы писали:


sto>В принципе, все вы правильно написали. Либо запрашивать коллекцию ContactInfo и удалять явно отсутствующие контакты, либо завести специальный класс коллекции, который будет хранить в отдельном свойстве удаленные записи, и во всех контрактах использовать его. Не знаю, насколько удастся подружить это с Сильверлайтом.

Да, вы правы, второй вариант в Сильверлайт так просто не пробросишь, поэтому я его не упомянул.


sto>Мне кажется, что дополнительный запрос к базе данных не сильно скажется на производительности, по сравнению с кучей UPDATE/INSERT, которые последуют за ним.

Да, действительно, я что-то не обратил на это внимания.

sto>Хуже, если у вас есть оптимистическая блокировка в таблице для ContactInfo. В случае перезапроса данных с сервера и аттача возможно поле версии будет заменено более новым.

sto>Но тут надо проверять.
Спасибо. Вроде бы у меня тут проблем нет, но буду иметь это введу.

sto>Если ContactInfo не является частью связи один-ко-многим со стороны "один" (никакие другие таблицы не содержат ссылку на таблицу для ContactInfo), то возможно вам подойдет перед сохранением записи удалять, а потом только добавлять.

Не хотелось бы с этим связываться, т.к. такой способ весьма опасен, если смотреть в будущее.


Я задам еще один вопрос, буду рад так же услышать мнение. Дело в том, что в Customer помимо ContactInfo еще около 6 подобных коллекций, причем (к моему сожалению ), некоторые из них сложные (т.е. каждый элемент содержит подколлекцию).

Раз сделать апдейт одной строчкой не получится, то единственная моя возможность жить по-человечески, это создавать репозиторий, который будет примерно так работать:


        public void UpdateCustomer(Customer customer)
        {
            myEntityContext.Customers.Attach(customer);
            ...
            myEntityContext.SaveChanges();

            UpdateCustomerContactInfoCollection(customer.Id, customer.ContactInfoList);
            ...
            UpdateCustomerSomethingCollection(customer.Id, customer.SomethingList);
        }
        
        public void UpdateCustomerContactInfoCollection(int customerId, List<ContactInfo> contactInfoList)
        {
            List<ContactInfo> deleteList = GenerateDeleteList(customerId, contactInfoList);
            List<ContactInfo> insertList = GenerateInsertList(customerId, contactInfoList);
            List<ContactInfo> updateList = GenerateUpdateList(customerId, contactInfoList);

            foreach (var contactInfo in deleteList)
                DeleteContactInfo(contactInfo);

            foreach (var contactInfo in insertList)
                AddContactInfo(contactInfo);

            foreach (var contactInfo in updateList)
                UpdateContactInfo(contactInfo);
        }

        public void DeleteContactInfo(ContactInfo contactInfo)
        {
            myEntityContext.ContactInfos.Remove(contactInfo);
        }
        public void AddContactInfo(ContactInfo contactInfo)
        {
            myEntityContext.ContactInfos.Add(contactInfo);
        }
        ...


Т.е. выделить каждую операцию в свой метод, а потом писать изменение больших объектов, как композицию изменений мелких.

Я правильно понял, что теперь это не сценарий транзакции, а модель предметной области (в терминологии Фаулера)? К сожалению, спросить не у кого, живу на самообразовании . Просто у меня сложилось ощущение, что наконец-то я увидел пример, для которого имеет смысл делать модель предметной области.
Re[3]: [Entity Framework] Update EntityCollection
От: sto Украина http://overstore.codeplex.com
Дата: 05.03.10 12:21
Оценка:
Здравствуйте, NoHate, Вы писали:

NH>Я задам еще один вопрос, буду рад так же услышать мнение. Дело в том, что в Customer помимо ContactInfo еще около 6 подобных коллекций, причем (к моему сожалению ), некоторые из них сложные (т.е. каждый элемент содержит подколлекцию).


NH>Раз сделать апдейт одной строчкой не получится, то единственная моя возможность жить по-человечески, это создавать репозиторий, который будет примерно так работать:



NH>
NH>        public void UpdateCustomer(Customer customer)
NH>        {
NH>            myEntityContext.Customers.Attach(customer);
NH>            ...
NH>            myEntityContext.SaveChanges();

NH>            UpdateCustomerContactInfoCollection(customer.Id, customer.ContactInfoList);
NH>            ...
NH>            UpdateCustomerSomethingCollection(customer.Id, customer.SomethingList);
NH>        }
        
NH>        public void UpdateCustomerContactInfoCollection(int customerId, List<ContactInfo> contactInfoList)
NH>        {
NH>            List<ContactInfo> deleteList = GenerateDeleteList(customerId, contactInfoList);
NH>            List<ContactInfo> insertList = GenerateInsertList(customerId, contactInfoList);
NH>            List<ContactInfo> updateList = GenerateUpdateList(customerId, contactInfoList);

NH>            foreach (var contactInfo in deleteList)
NH>                DeleteContactInfo(contactInfo);

NH>            foreach (var contactInfo in insertList)
NH>                AddContactInfo(contactInfo);

NH>            foreach (var contactInfo in updateList)
NH>                UpdateContactInfo(contactInfo);
NH>        }

NH>        public void DeleteContactInfo(ContactInfo contactInfo)
NH>        {
NH>            myEntityContext.ContactInfos.Remove(contactInfo);
NH>        }
NH>        public void AddContactInfo(ContactInfo contactInfo)
NH>        {
NH>            myEntityContext.ContactInfos.Add(contactInfo);
NH>        }
NH>        ...
NH>


NH>Т.е. выделить каждую операцию в свой метод, а потом писать изменение больших объектов, как композицию изменений мелких.


Я попытался написать обобщенный метод сохранения таких коллекций, однако он не работает:

public static class DataContextExtensions
    {
        /// <summary>
        /// Saves the collection of the detached entities to the database.
        /// Method automatically determines state of each entity, including deleted entities.
        /// </summary>
        /// <typeparam name="TEntity">The type of the collection item.</typeparam>
        /// <param name="context">The context.</param>
        /// <param name="collectionToSave">The collection to save.</param>
        /// <param name="getOriginalCollection">Function returns original collection currently stored in database.</param>
        public static void SaveDetachedCollection<TEntity>(this ObjectContext context,
            IEnumerable<TEntity> collectionToSave,
            Func<IQueryable<TEntity>> getOriginalCollection)
            where TEntity : EntityObject
        {
            if (Object.ReferenceEquals(context, null))
                throw new ArgumentNullException("context");
            if (Object.ReferenceEquals(collectionToSave, null))
                throw new ArgumentNullException("collectionToSave");
            if (Object.ReferenceEquals(getOriginalCollection, null))
                throw new ArgumentNullException("getOriginalCollection");
            var originalCollection = getOriginalCollection().ToList();
            foreach (var entity in collectionToSave)
            {
                // Если объект вновь создан, его свойство EntityKey == null, 
                // и метод Attach() бросает исключение.
                if (entity.EntityState == EntityState.Detached)
                    context.Attach(entity);
                originalCollection.Remove(entity);
            }
            foreach (var deletedEntity in originalCollection)
                context.DeleteObject(deletedEntity);
        }
    }


Его можно конечно модифицировать, чтобы он работал, однако смотрится это крайне криво:

public static class DataContextExtensions
    {
        /// <summary>
        /// Saves the collection of the detached entities to the database.
        /// Method automatically determines state of each entity, including deleted entities.
        /// </summary>
        /// <typeparam name="TEntity">The type of the collection item.</typeparam>
        /// <param name="context">The context.</param>
        /// <param name="collectionToSave">The collection to save.</param>
        /// <param name="getOriginalCollection">Function returns original collection currently stored in database.</param>
        public static void SaveDetachedCollection<TEntity>(this ObjectContext context,
            IEnumerable<TEntity> collectionToSave,
            Func<IQueryable<TEntity>> getOriginalCollection,
            Action<TEntity> addNewEntity)
            where TEntity : EntityObject
        {
            if (Object.ReferenceEquals(context, null))
                throw new ArgumentNullException("context");
            if (Object.ReferenceEquals(collectionToSave, null))
                throw new ArgumentNullException("collectionToSave");
            if (Object.ReferenceEquals(getOriginalCollection, null))
                throw new ArgumentNullException("getOriginalCollection");
            if (Object.ReferenceEquals(addNewEntity, null))
                throw new ArgumentNullException("addNewEntity");

            var originalCollection = getOriginalCollection().ToList();

            foreach (var entity in collectionToSave)
            {
                if (entity.EntityKey == null)
                    addNewEntity(entity);
                else
                {
                    if (entity.EntityState == EntityState.Detached)
                        context.Attach(entity);
                    originalCollection.Remove(entity);
                }
            }
            foreach (var deletedEntity in originalCollection)
                context.DeleteObject(deletedEntity);
        }


NH>Я правильно понял, что теперь это не сценарий транзакции, а модель предметной области (в терминологии Фаулера)? К сожалению, спросить не у кого, живу на самообразовании . Просто у меня сложилось ощущение, что наконец-то я увидел пример, для которого имеет смысл делать модель предметной области.


По идее, модель предметной области к способу сохранения отношения не имеет. Здесь — кривой и требующий большого допиливания паттерн Unit of Work.
There is no such thing as the perfect design.
Re[4]: [Entity Framework] Update EntityCollection
От: NoHate  
Дата: 09.03.10 22:28
Оценка:
Здравствуйте, sto, Вы писали:

sto>Я попытался написать обобщенный метод сохранения таких коллекций, однако он не работает:

sto>...
sto>Его можно конечно модифицировать, чтобы он работал, однако смотрится это крайне криво:

sto>По идее, модель предметной области к способу сохранения отношения не имеет. Здесь — кривой и требующий большого допиливания паттерн Unit of Work.


Большое спасибо за информацию. Буду изучать. Но думаю, что предпочту все-таки обобщенный метод для обновления коллекций, уж больно трудоемким мне кажется Unit of Work.
Re[5]: [Entity Framework] Update EntityCollection
От: NoHate  
Дата: 09.03.10 22:34
Оценка:
На всякий случай, для тех, кто будет искать информацию по данной проблеме.

В июньском MSDN magazine была статья как раз по этой теме:

The Unit Of Work Pattern And Persistence Ignorance
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.