【问题标题】:Proper way to dispose DbContext when one method calls another and both use DbContext当一种方法调用另一种方法并且都使用 DbContext 时处理 DbContext 的正确方法
【发布时间】:2016-11-17 00:29:02
【问题描述】:

我是 Entity Framework 的新手,当一种方法调用另一种方法时,我对处置我的 DbContext 的正确方法感到困惑。这两个都被独立调用,这就是 DeleteSheet 是独立的原因。但是,当父(链)被删除时,DeleteChain 调用 DeleteSheet 来删除子。

我尝试在每种方法中处理,但是当我运行 DeleteChain 时,我得到:

The operation cannot be completed because the DbContext has been disposed.

db.Chains.Remove(chain);

我猜是因为我在删除子项时在 DeleteSheet 中处理了它。

这是我的方法:

public class ControllerHelper
{
    private UranusContext db = new UranusContext();

    public void DeleteChain(int? chainId)
    {
        var chain = db.Chains.Include(c => c.Sheets)
            .SingleOrDefault(c => c.ChainId == chainId);

        // Removes sheets (children)
        foreach (var sheet in chain.Sheets.ToList())
        {
            DeleteSheet(sheet.SheetId);
        }

        db.Chains.Remove(chain);
        db.SaveChanges();
        db.Dispose();
    }

    public void DeleteSheet(int? sheetId)
    {
        var sheet = db.Sheets.Include(s => s.FileDetails)
            .Include(s => s.SheetsCounties)
            .SingleOrDefault(s => s.SheetId == sheetId);

        foreach (var fileDetails in sheet.FileDetails.ToList())
        {
            db.FileDetails.Remove(fileDetails);
        }

        foreach (var sheetsCounties in sheet.SheetsCounties.ToList())
        {
            db.SheetsCounties.Remove(sheetsCounties);
        }

        db.Sheets.Remove(sheet);
        db.SaveChanges();

        db.Dispose();
    }
}

在这种情况下如何正确处理 DbContext?我很困惑,因为如果我只在 DeleteChain 中处理,然后在 DeleteChain 之外调用 DeleteSheet,它就不会处理。

编辑:尝试 #2。我将每个包装在使用中并单独处理它们。但是,当我删除 DeleteSheet 中的孩子时,DeleteChain 并没有意识到我这样做并抛出:The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted. on db.SaveChanges() in DeleteChain

尝试 #2。

public void DeleteChain(int? chainId)
{
    using (UranusContext db = new UranusContext())
    {
        var chain = db.Chains.Include(c => c.Sheets)
            .SingleOrDefault(c => c.ChainId == chainId);

        // Removes sheets (children)
        foreach (var sheet in chain.Sheets.ToList())
        {
            DeleteSheet(sheet.SheetId);
            //db.Sheets.Remove(sheet);
        }

        db.Chains.Remove(chain);
        db.SaveChanges();
    }
}

public void DeleteSheet(int? sheetId)
{
    using (UranusContext db = new UranusContext())
    {
        var sheet = db.Sheets.Include(s => s.FileDetails)
            .Include(s => s.SheetsCounties)
            .SingleOrDefault(s => s.SheetId == sheetId);

        foreach (var fileDetails in sheet.FileDetails.ToList())
        {
            db.FileDetails.Remove(fileDetails);
        }

        foreach (var sheetsCounties in sheet.SheetsCounties.ToList())
        {
            db.SheetsCounties.Remove(sheetsCounties);
        }

        db.Sheets.Remove(sheet);
        db.SaveChanges();
    }
}

【问题讨论】:

    标签: c# asp.net-mvc entity-framework


    【解决方案1】:

    您可能希望在单独的方法中移动工作表删除逻辑,并从 DeleteChain 和 DeleteSheet 中调用它,如下所示:

    public class ControllerHelper
    {
        public void DeleteChain(int? chainId) {
            using (var db = new UranusContext()) {
                var chain = db.Chains.Include(c => c.Sheets)
                    .SingleOrDefault(c => c.ChainId == chainId);
    
                // Removes sheets (children)
                foreach (var sheet in chain.Sheets.ToList()) {
                    DeleteSheet(db, sheet);
                }
    
                db.Chains.Remove(chain);
                db.SaveChanges();
            }
        }
    
        public void DeleteSheet(int? sheetId) {
            using (var db = new UranusContext()) {
                var sheet = db.Sheets.Include(s => s.FileDetails)
                    .Include(s => s.SheetsCounties)
                    .SingleOrDefault(s => s.SheetId == sheetId);
    
                DeleteSheet(db, sheet);
                db.SaveChanges();
            }
        }
    
        private void DeleteSheet(UranusContext db, Sheet sheet) {
            foreach (var fileDetails in sheet.FileDetails.ToList()) {
                db.FileDetails.Remove(fileDetails);
            }
    
            foreach (var sheetsCounties in sheet.SheetsCounties.ToList()) {
                db.SheetsCounties.Remove(sheetsCounties);
            }
    
            db.Sheets.Remove(sheet);
        }
    }
    

    【讨论】:

    • 这似乎是最可行和最有效的。我只有一个问题:当你传递 UranusContext db 时,它不会创建一个新的 UranusContext 实例吗?是否已消除或增加任何不必要的开销?
    • 不,当您传递对上下文的引用时 - 没有创建任何内容(也没有任何开销)。
    【解决方案2】:

    最简单的方法是将 DbContext 作为参数传递:

    public class ControllerHelper
    {
        public void DeleteChain(int? chainId, UranusContext db)
        {
            var chain = db.Chains.Include(c => c.Sheets)
                .SingleOrDefault(c => c.ChainId == chainId);
    
            // Removes sheets (children)
            foreach (var sheet in chain.Sheets.ToList())
            {
                DeleteSheet(sheet.SheetId, db);
            }
    
            db.Chains.Remove(chain);
        }
    
        public void DeleteSheet(int? sheetId, UranusContext db)
        {
            var sheet = db.Sheets.Include(s => s.FileDetails)
                .Include(s => s.SheetsCounties)
                .SingleOrDefault(s => s.SheetId == sheetId);
    
            foreach (var fileDetails in sheet.FileDetails.ToList())
            {
                db.FileDetails.Remove(fileDetails);
            }
    
            foreach (var sheetsCounties in sheet.SheetsCounties.ToList())
            {
                db.SheetsCounties.Remove(sheetsCounties);
            }
    
            db.Sheets.Remove(sheet);
        }
    }
    

    然后像这样使用它

    public void Foo(int? someChainId, int? someSheetId, bool required = false)
    {
        ControllerHelper hlp = new ControllerHelper();
        using (UranusContext db = new UranusContext())
        {
            hlp.DeleteChain(someChainId);
    
            if(_required)
                hlp.DeleteSheet(int? someSheetId);
    
            db.SaveChanges();
        }
    }
    

    【讨论】:

      【解决方案3】:

      尝试使用模式并将上下文作为参数传递给 DeleteSheet 方法:

          public class ControllerHelper
      {
          public void DeleteChain(int? chainId)
          {
          using (UranusContext db = new UranusContext())
          {
                  var chain = db.Chains.Include(c => c.Sheets)
                      .SingleOrDefault(c => c.ChainId == chainId);
      
                  // Removes sheets (children)
                  foreach (var sheet in chain.Sheets.ToList())
                  {
                      DeleteSheet(sheet.SheetId, db);
                  }
      
                  db.Chains.Remove(chain);
                  db.SaveChanges();
          }
          }
      
          public void DeleteSheet(int? sheetId, UranusContext db)
          {
              var sheet = db.Sheets.Include(s => s.FileDetails)
                  .Include(s => s.SheetsCounties)
                  .SingleOrDefault(s => s.SheetId == sheetId);
      
              foreach (var fileDetails in sheet.FileDetails.ToList())
              {
                  db.FileDetails.Remove(fileDetails);
              }
      
              foreach (var sheetsCounties in sheet.SheetsCounties.ToList())
              {
                  db.SheetsCounties.Remove(sheetsCounties);
              }
      
              db.Sheets.Remove(sheet);
              db.SaveChanges();
          }
      }
      

      这样,您可以确保只有在所有删除都完成后才会释放上下文。我总是尽量避免一次使用多个上下文,我以前被这种情况严重烧伤!

      【讨论】:

      • 我遇到的问题是我在控制器类中自己调用了 DeleteSheet,这使得我必须在那里创建另一个 DbContext 才能以这种方式调用它。我仍然不喜欢它,但也许我很挑剔。
      • 解决了问题,但调用了“SaveChanges()”chain.Sheets-times,如果SaveChanges() 有任何重载,这可能会成为问题。
      • 写一个 DeleteSheet 的覆盖,它接受 Id 和上下文,并保留你拥有的其他东西。
      【解决方案4】:

      在您的设计中,ControllerHelperUranusContext 的组合。这意味着您设计了只要ControllerHelper 存在,UranusContext 就存在,并且您希望UranusCibtextControllerHelper 不再存在时立即停止存在。

      通过使它成为一个组合,你向你的类的用户保证,只要你的类存在,你的所有函数都可以工作。因此,在您的用户通知您他们不再需要您的功能之前,您不应该在您的任何功能中使用Dispose() UranusContext。这可以通过使您的ControllerHelper IDisposable 来完成

      如果有任何类实现了 IDisposable,则此类的设计者鼓励用户在不再需要该对象的功能后尽快调用Dispose()

      在你的情况下,代码是:

      public class ControllerHelper : IDisposable()
      {
          private UranusContext db = new UranusContext();
      
          public void Dispose()
          {
              if (this.db != null)
              {
                  this.db.Dispose();
                  this.db = null;
              }
              GC.SuppressFinalize(this);
          }
      
          public void DeleteChain(int? chainId)
          {
             // Do what you need to do, but don't Dispose()
             // nor use using, we want db to be functional
          }
          public void DeleteSheet(int? sheetId)
          {
             // Do what you need to do, but don't Dispose()
             // nor use using, we want db to be functional
          }
      }
      

      用法如下:

      using (var controllerHelper = new ControllerHelper())
      {
          // call functions from controllerhelper
      }
      // the controllerHelper is not needed anymore, Dispose() is called automatically
      // and thus db is Disposed
      

      【讨论】:

      • 我更喜欢这个。拥有using (UranusContext db = new UranusContext()) 会确保妥善处置吗?我遵循@Evk 的解决方案,但我仍然怀疑将 UranusContext 作为参数传递并且它没有被正确处理。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 1970-01-01
      • 2010-11-20
      • 2017-10-30
      • 2020-08-02
      • 1970-01-01
      相关资源
      最近更新 更多