Проблема Прикрепление объекта к контексту данных Telerik OpenAccess
Я пишу несколько тестов для определения уровня хранилища библиотеки, построенной на Telerik OpenAccess ORM, и сталкиваюсь с некоторыми проблемами при управлении контекстом.
Я создаю новый объект RegionEntity и добавляю его в базу данных. Я использую оператор using, чтобы контекст очищался после себя. Я дополнительно создаю Отдельную копию добавленного RegionEntity, чтобы впоследствии ее можно было снова присоединить к контексту.
private RegionEntity AddTestRegionToTable()
{
String regionName = Guid.NewGuid().ToString();
RegionEntity newRegion = new RegionEntity () { /*...property assignment goes here ...*/ };
RegionEntity ret = null;
using (DbContext ctx = new DbContext())
{
ctx.Add(newRegion);
ctx.SaveChanges();
ret = ctx.CreateDetachedCopy<RegionEntity>(newRegion);
}
return ret;
}
Пока что... нет проблем. В моем TestMethod ниже я вызываю вышеупомянутый метод и получаю Detached RegionEntity. (Я вытащил свои заявления об утверждении, поскольку они несущественны для вопроса). Затем я передаю сущность методу Respository, который я хочу протестировать.
[TestMethod]
public void RemoveRegion_Success()
{
//
// Assemble
RegionEntity origEntity = AddTestRegionToTable();
//
// Act
deletedEntity = RegionRepository.RemoveEntity<RegionEntity>(origEntity);
//
// Assert
/* asserts go here */
}
Для полноты изложения ниже я включил ВСЕ оставшийся код в точности так, как он отображается в моем приложении. Методы хранилища являются универсальными (опять же... не должны относиться к проблеме). Первый метод - это тот, который вызывается тестовым методом, передавая регион в качестве параметра entityToRemove. Этот метод, в свою очередь, вызывает метод DBUtils, GetContext(), который либо извлекает DbContext из сущности, либо... если он не может быть получен... создает новый контекст для использования. В нашем примере создается новый контекст.
public class RegionRepository
{
public static T RemoveEntity<T>(T entityToRemove) where T : class
{
T ret = null;
using (DbContext ctx = DbUtils.GetContext<T>(entityToRemove))
{
ret = RemoveEntity<T>(ctx, entityToRemove);
ctx.SaveChanges();
}
return ret;
}
public static T RemoveEntity<T>(DbContext ctx, T entityToRemove) where T : class
{
//
// first chcek to see if the listingToUpdate is attached to the context
ObjectState state = OpenAccessContext.PersistenceState.GetState(entityToRemove);
//
//If the object is detached then attach it
if (state.HasFlag(ObjectState.Detached))
{
ctx.AttachCopy<T>(entityToRemove);
}
//
// confirm that the DETACHED flag is no longer present.
ObjectState state2 = OpenAccessContext.PersistenceState.GetState(entityToRemove);
if (state2.HasFlag(ObjectState.Detached))
{
throw new Exception("Unable to attach entity to context");
}
ctx.Delete(entityToRemove);
return entityToRemove;
}
}
public class DBUtils
{
public static DbContext GetContext<T>(T entity)
{
DbContext ret = OpenAccessContextBase.GetContext(entity) as DbContext;
if(ret == null)
{
ret = new DbContext();
}
return ret;
}
}
В любом случае, метод затем передает этот контекст и сущность в качестве параметров для перегрузки. Этот метод принимает DbContext в качестве дополнительного параметра (позволяет использовать один контекст в многошаговых рабочих процессах). Таким образом, используемый контекст должен быть тем, который мы извлекли из сущности или создали в нашем методе GetContext(). Затем я проверяю, связана ли сущность с контекстом или нет. В этом сценарии я получаю флаг "Detached" в качестве одного из флагов состояния (другие - MaskLoaded | MaskManaged | MaskNoMask), поэтому процесс присоединяет объект к контексту, и после второй проверки я подтверждаю, что флаг Detached не имеет дольше присутствует.
Как оказалось, сущность НЕ привязана... и исключение выбрасывается.
Я прочитал документацию Telerik об отделении и присоединении объектов к контексту... Присоединение и отключение объектов
1 ответ
По дизайну ObjectState
Это флаги enum, которые содержат как основные значения, которые формируют постоянные состояния доступа к данным, так и сами постоянные состояния.
В этом перечислении Detached
это значение, которое участвует в трех отключенных постоянных состояниях: DetachedClean, DetachedDirty и DetachedNew. Вы можете найти больше информации о значениях и состояниях в этой статье.
Когда вы отсоединяете объект от контекста, его состояние - DetachedClean. Если в этот момент вы измените какое-либо из свойств, состояние объекта станет DetachedDirty. Если вы прикрепите объект обратно, он останется в состоянии до прикрепления. Проще говоря, действие по прикреплению объекта не меняет его состояния.
Другими словами, проверка на Detached
причина, по которой вы получаете исключение "Невозможно присоединить сущность к контексту". Это значение всегда будет доступно в состоянии вашего объекта.
Поскольку я читаю код вперед, в этой строке:
ctx.Delete(entityToRemove);
В любом случае вы получите исключение, потому что Доступ к данным не позволяет вам удалять объекты, которые извлекаются через другие экземпляры контекста. Исключение составляет:
InvalidOperationException: ссылки на объекты между двумя различными областями объекта не допускаются.
Надеюсь, это поможет.
- = РЕДАКТИРОВАТЬ =-
Когда вы присоединяете определенный объект к экземпляру контекста и вызываете метод SaveChanges(), Data Access автоматически решает, вставить новую строку в базу данных или обновить существующую строку. В связи с этим сценарии вставки и обновления обрабатываются API Attach / Detach.
Что касается сценария удаления, у вас есть два варианта:
Чтобы извлечь объект из базы данных и удалить его с помощью метода Delete() (и вызвать SaveChanges()), например:
var myObj = ctx.RegionEntities.First(r => r.Id == entityToRemove.Id); ctx.Delete(myObj); ctx.SaveChanges();
Чтобы использовать функцию BulkDelete, как это:
var myObj = ctx.RegionEntities.Where(r => r.Id == entityToRemove.Id); int deletedObjects = myObj.DeleteAll();
Что касается первой опции, вам нужно рассмотреть, вызывать ли SaveChanges() после присоединения объекта. Это хорошая идея, если есть изменения, которые вы хотели бы сохранить до удаления объекта. Кроме того, когда вы используете метод Delete() контекста, вам необходимо зафиксировать изменение с помощью метода SaveChanges(), прежде чем вы удалите текущий экземпляр контекста. Если вы этого не сделаете, транзакция будет отменена, то есть объект не будет удален. Подробности об обработке транзакций доступны здесь.
Вторая опция, Bulk Delete, выполняет операции удаления в отдельной транзакции после вызова метода DeleteAll(). Поэтому любые другие незафиксированные изменения не затрагиваются. Тем не менее, вам необходимо рассмотреть вызов SaveChanges() после присоединения объекта, особенно если присоединенный и удаленный объекты - это один и тот же объект.