2

我正在编写一些测试来扩展基于 Telerik OpenAccess ORM 构建的库的存储库层,并且在管理上下文时遇到了一些问题。

我正在创建一个新的 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 中,我调用上述方法并收到一个分离的 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()方法中创建的上下文。然后我检查实体是否附加到上下文。在这种情况下,我将“已分离”标志作为状态标志之一(其他标志为MaskLoaded | MaskManaged | MaskNoMask),因此该过程随后将实体附加到上下文,并在第二次检查时确认已分离标志为否存在时间更长。

事实证明,实体没有被附加......并且正在引发异常。

我已阅读有关分离和附加对象到上下文的 Telerik 文档...附加和分离对象

4

1 回答 1

1

按照设计ObjectState,标志枚举既包含形成数据访问持久状态的基本值,又包含持久状态本身。

在这个枚举中,Detached是一个参与三个分离的持久状态的值:DetachedCleanDetachedDirtyDetachedNew您可以在本文中找到有关值和状态的更多信息。

当你从上下文中分离一个对象时,它的状态是DetachedClean。如果此时您更改任何属性,则对象的状态将变为DetachedDirty。如果您将对象附加回来,它将保持附加之前的状态。简单地说,附加对象的动作不会改变它的状态。

换句话说,检查Detached是您获得“无法将实体附加到上下文”异常的原因。此值将始终在您的对象状态下可用。

当我正在阅读代码时,在这一行:

ctx.Delete(entityToRemove);

无论如何,您都会遇到异常,因为数据访问不允许您删除通过上下文的另一个实例检索到的对象。例外是:

InvalidOperationException:不允许两个不同对象范围之间的对象引用。

我希望这有帮助。

-= 编辑 =-

当您将某个对象附加到上下文的实例并调用 SaveChanges() 方法时,数据访问将自动决定是在数据库中插入新行还是更新现有行。在这方面,插入和更新场景由 Attach / Detach API 处理。

关于删除方案,您有两种选择:

  1. 从数据库中检索对象并通过 Delete() 方法将其删除(并调用 SaveChanges()),如下所示:

       var myObj = ctx.RegionEntities.First(r => r.Id == entityToRemove.Id);
       ctx.Delete(myObj);
       ctx.SaveChanges();
    
  2. 要像这样使用BulkDelete功能:

       var myObj = ctx.RegionEntities.Where(r => r.Id == entityToRemove.Id);
       int deletedObjects = myObj.DeleteAll();
    

对于第一个选项,您需要考虑的是在附加对象后是否调用 SaveChanges()。如果您希望在删除对象之前保留更改,那么这样做是个好主意。此外,当您使用上下文的 Delete() 方法时,您需要在释放上下文的当前实例之前通过 SaveChanges() 方法提交更改。如果不这样做,事务将被回滚,这意味着对象不会被删除。有关事务处理的详细信息可在此处获得。

第二个选项,批量删除,在调用 DeleteAll() 方法时在单独的事务中执行删除操作。因此,任何其他未提交的更改都不会受到影响。不过,您需要考虑在附加对象后调用 SaveChanges(),尤其是在附加对象和删除对象是同一个对象的情况下。

于 2015-06-23T15:54:57.527 回答