我正在编写一些测试来优化在Telerik OpenAccess ORM上构建的库的存储库层,并且遇到了管理Context的一些问题。
我正在创建一个新的RegionEntity对象并将其添加到数据库中。我使用using语句,以便上下文自行清理。我另外创建了添加的RegionEntity的Detached副本,以便稍后可以将它重新附加到上下文。
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 */
}
为了完整起见,下面我已经包含了所有剩余的代码,就像它在我的应用程序中出现的那样。存储库方法是Generic(再次......不应该与问题相关)。第一种方法是由测试方法调用的方法,将该区域作为 entityToRemove 参数传递。此方法依次调用DBUtils方法 GetContext() ,它将从实体中检索DbContext,或者......如果无法派生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() 方法中创建的上下文。然后我检查实体是否附加到上下文。在这种情况下,我得到了一个标记&#34; Detached&#34;作为状态标志之一(其他是 MaskLoaded | MaskManaged | MaskNoMask ),然后进程将实体附加到上下文,并在第二次检查时确认已离开标志不再存在。
事实证明实体没有附加......并且抛出了异常。
我已阅读Telerik有关分离和将对象附加到上下文的文档...... Attaching and Detaching Objects
答案 0 :(得分:1)
设计ObjectState
是标志枚举,它包含形成数据访问持久状态的基本值和持久状态本身。
在此枚举中,Detached
是一个参与三个分离的持久状态的值: DetachedClean , DetachedDirty 和 DetachedNew 。您可以在this article中找到有关值和状态的更多信息。
从上下文中分离对象时,其状态为 DetachedClean 。如果此时您更改了任何属性,则对象的状态将变为 DetachedDirty 。如果将对象附加回来,它将保持在附件之前的状态。简单地说,附加物体的动作不会改变它的状态。
换句话说,检查Detached
是您获得“无法将实体附加到上下文”异常的原因。此值始终在对象的状态下可用。
当我正在阅读代码时,在这一行:
ctx.Delete(entityToRemove);
无论如何,您将获得异常,因为Data Access不允许您删除通过上下文的另一个实例检索的对象。例外是:
InvalidOperationException:不允许在两个不同的对象范围之间进行对象引用。
我希望这会有所帮助。
- =编辑= -
将某个对象附加到上下文的实例并调用SaveChanges()方法时,Data Access将自动决定是在数据库中插入新行还是更新现有行。在这方面,插入和更新方案由Attach / Detach API处理。
关于删除方案,您有两个选择:
要从数据库中检索对象并通过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()方法提交更改。如果不这样做,事务将回滚,这意味着不会删除该对象。有关交易处理的详细信息可用here。
第二个选项Bulk Delete在调用DeleteAll()方法时在单独的事务中执行删除操作。因此,任何其他未提交的更改都不会受到影响。然而,在附加对象之后,您需要考虑调用SaveChanges(),特别是如果附加对象和已删除对象是同一个对象。