Здравствуйте, NoHate, Вы писали:
NH>Можно воспользоваться этим путем. Но в таком случае нужно разделить все операции на три категории: add, modify, delete, а затем пробежать по каждой категории и сделать для каждого элемента соответствующий запрос. NH>Но тогда возникает другая проблема: кто-то должен генерировать эти списки операций. Тут варианта два: либо записывать действия на стороне клиента, либо определять изменения на стороне сервера. NH>• Если на стороне клиента, то нужно посылать несколько списков (3 вместо одного). Это сильно усложнит реализацию GUI, по причине того, что нужно будет постоянно следать за всеми действиями пользователя + достаточно сильно усложнится модель. NH>• Если на стороне сервера, то нужно будет сделать еще один запрос в БД, чтобы выяснить, какие записи нужно удалить, какие добавить, какие обновить.
NH>Все это видится мне какими-то велосипедами. Ведь EF сам умеет правильно добавлять реляции, умеет их удалять, при удалении родительского элемента. А апдейта разве нет?
NH>Буду очень рад, если кто-нибудь укажет, что почитать, какой проект/кусок кода посмотреть или просто, в какую сторону мне двигаться. NH>Весь инет завален банальными примерами: GetEntity/UpdateEntity, а вот что-то приближенное к реальности найти тяжело.
NH>Сейчас перечитал – вроде бы доступно написал.
В принципе, все вы правильно написали. Либо запрашивать коллекцию ContactInfo и удалять явно отсутствующие контакты, либо завести специальный класс коллекции, который будет хранить в отдельном свойстве удаленные записи, и во всех контрактах использовать его. Не знаю, насколько удастся подружить это с Сильверлайтом.
Мне кажется, что дополнительный запрос к базе данных не сильно скажется на производительности, по сравнению с кучей UPDATE/INSERT, которые последуют за ним.
Хуже, если у вас есть оптимистическая блокировка в таблице для ContactInfo. В случае перезапроса данных с сервера и аттача возможно поле версии будет заменено более новым.
Но тут надо проверять.
Если ContactInfo не является частью связи один-ко-многим со стороны "один" (никакие другие таблицы не содержат ссылку на таблицу для ContactInfo), то возможно вам подойдет перед сохранением записи удалять, а потом только добавлять.
Разбираюсь с 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), а потом сохраняет изменения на сервер.
Вопрос в том, как сохранить эти изменения.
Я не видел решения этой проблемы, поэтому просто предложу варианты:
Можно воспользоваться этим путем. Но в таком случае нужно разделить все операции на три категории: add, modify, delete, а затем пробежать по каждой категории и сделать для каждого элемента соответствующий запрос.
Но тогда возникает другая проблема: кто-то должен генерировать эти списки операций. Тут варианта два: либо записывать действия на стороне клиента, либо определять изменения на стороне сервера.
• Если на стороне клиента, то нужно посылать несколько списков (3 вместо одного). Это сильно усложнит реализацию GUI, по причине того, что нужно будет постоянно следать за всеми действиями пользователя + достаточно сильно усложнится модель.
• Если на стороне сервера, то нужно будет сделать еще один запрос в БД, чтобы выяснить, какие записи нужно удалить, какие добавить, какие обновить.
Все это видится мне какими-то велосипедами. Ведь EF сам умеет правильно добавлять реляции, умеет их удалять, при удалении родительского элемента. А апдейта разве нет?
Буду очень рад, если кто-нибудь укажет, что почитать, какой проект/кусок кода посмотреть или просто, в какую сторону мне двигаться.
Весь инет завален банальными примерами: GetEntity/UpdateEntity, а вот что-то приближенное к реальности найти тяжело.
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);
}
...
Т.е. выделить каждую операцию в свой метод, а потом писать изменение больших объектов, как композицию изменений мелких.
Я правильно понял, что теперь это не сценарий транзакции, а модель предметной области (в терминологии Фаулера)? К сожалению, спросить не у кого, живу на самообразовании . Просто у меня сложилось ощущение, что наконец-то я увидел пример, для которого имеет смысл делать модель предметной области.
Здравствуйте, NoHate, Вы писали:
NH>Я задам еще один вопрос, буду рад так же услышать мнение. Дело в том, что в Customer помимо ContactInfo еще около 6 подобных коллекций, причем (к моему сожалению ), некоторые из них сложные (т.е. каждый элемент содержит подколлекцию).
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.
Здравствуйте, sto, Вы писали:
sto>Я попытался написать обобщенный метод сохранения таких коллекций, однако он не работает: sto>... sto>Его можно конечно модифицировать, чтобы он работал, однако смотрится это крайне криво:
sto>По идее, модель предметной области к способу сохранения отношения не имеет. Здесь — кривой и требующий большого допиливания паттерн Unit of Work.
Большое спасибо за информацию. Буду изучать. Но думаю, что предпочту все-таки обобщенный метод для обновления коллекций, уж больно трудоемким мне кажется Unit of Work.