【问题标题】:A new object is detached from a data context新对象与数据上下文分离
【发布时间】:2019-11-04 18:02:46
【问题描述】:

我在后台工作人员中添加了一个新对象,新对象已成功添加,但如果尝试删除它,则会出现错误,因为新对象已从数据上下文中分离出来。

后台工作人员 Do_Work 事件

private void _anotherThread_DoWork(object sender, DoWorkEventArgs e)
    {
        testdbEntities context = new testdbEntities();

        _item = new item();
        this.txbItemName.Dispatcher.Invoke(new Action(delegate () { _item.name = this.txbItemName.Text; }));

        context.item.Add(_item);
        Application.Current.Dispatcher.BeginInvoke(new Action(() => this.ocItems.Add(_item)));

        context.SaveChanges();
    }

删除按钮(在主线程上) ,如果我附加了一个新对象,它就可以工作

private void btnDelete_Click(object sender, RoutedEventArgs e)
    {
        Item _selectedItem = (Item)lbItems.SelectedItem;

        if (_context.Entry(_selectedItem).State == EntityState.Detached)
            _context.Item.Attach(_selectedItem);

        _context.Item.Remove(_selectedItem);
        _context.SaveChanges();
    }

我的问题是为什么新对象没有附加到数据上下文?我应该附加在后台工作人员中添加的每个新对象吗?

谢谢

【问题讨论】:

  • 您如何/在哪里管理您的上下文?是相同的背景吗?你知道每个线程(bgw)都应该有自己的吗?
  • @HenkHolterman 是的,在 bgw 中,我启动了我的数据库上下文的新实例
  • 您能否更新您的问题并添加创建新对象的相关代码段?
  • @devlincarnate 我更新了我的问题。
  • 这似乎很明显:这里有 2 个数据上下文。一个条目只能属于一个。

标签: c# wpf entity-framework backgroundworker


【解决方案1】:

我强烈建议不要使用实体,尤其是控件绑定中的跟踪实体。被跟踪的实体应该只存在于 DbContext 中,并且您创建的任何 DbContext 都应该具有定义的生命周期范围。 (丢弃)

因此,在您的后台作业中,应该在 using 块内新建一个 DbContext,以确保它被释放。

private void _anotherThread_DoWork(object sender, DoWorkEventArgs e)
{
    using(testdbEntities context = new testdbEntities())
    {
        _item = new item();
        this.txbItemName.Dispatcher.Invoke(new Action(delegate () { _item.name = this.txbItemName.Text; }));

        context.item.Add(_item);
        Application.Current.Dispatcher.BeginInvoke(new Action(() => this.ocItems.Add(_item)));

        context.SaveChanges();
        context.Detach(_item); 
    }
}

然后,当您使用删除按钮时,如果您正在处理长期存在的 DbContext (_context),则检查上下文以查看该实体是否已被跟踪,如果没有,则附加它(或代理到它) 并执行删除。如果已经被追踪,删除被追踪的:

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    Item _selectedItem = (Item)lbItems.SelectedItem;

    Item existingItem = _context.Item.Local.SingleOrDefault(x => x.ItemId == _selectedItem.ItemId);
    if (existingItem != null)
        _context.Item.Remove(existingItem);
    else
    {
        _context.Item.Attach(_selectedItem);
        _context.Item.Remove(_selectedItem);
    }
    _context.SaveChanges();
}

检查.Local 不会命中数据库,它只是查找该ID 的跟踪实体引用。如果找到,我们将其删除。如果没有,我们可以附加我们的并删除它以发出删除。

长期存在的 DbContexts 可能会导致您出现问题,因为通过各种操作,可能已经跟踪具有列表项 ID 的实体,因此使用存储在 List 控件中的陈旧副本的附加/分离状态不会'不可靠。

如果列表项返回要删除的实体的 ID 而不是未跟踪的实体引用,您仍然可以有效地删除实体:

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    int itemId = lbItems.SelectedItem.ItemId; // Where a simple view model was put in the SelectedItem, not an entity.

    Item existingItem = _context.Item.Local.SingleOrDefault(x => x.ItemId == itemId);
    if (existingItem != null)
        _context.Item.Remove(existingItem);
    else
    { 
        var tempItem = new Item { ItemId = itemId }; 
        _context.Item.Attach(tempItem);
        _context.Item.Remove(tempItem);
    }
    _context.SaveChanges();
}

没有太大不同,但消除了您的列表项包含完整实体的假设,而实际上它并不包含。列表视图模型也可以比整个实体更紧凑,需要更少的内存和时间来加载。处理附加和分离实体的风险在于,如果该 ID 的另一个实体已经被跟踪(由于编辑等)并且对于编辑等操作,这些实体可能是陈旧的,但仍需要根据上下文评估这些实体.其他应用程序/用户/进程可能已经更新了实体背后的数据。通过附加并执行诸如编辑和保存更改之类的操作,该实体仅保存该用户的最后一个已知状态,该状态可以并且将覆盖其他用户所做的更改。在处理并发编辑时,最好加载实体,检查行版本或时间戳以查看自上次检索以来它是否被修改,并在它有的情况下处理场景。您还可能遇到自当前会话检索到基础记录后可能已被删除的问题,在这种情况下,附加、更新和 SaveChanges 将导致“预期 1,发现 0”错误或类似错误。 (不确定,但这也可能发生在删除场景中。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-14
    • 2010-09-14
    相关资源
    最近更新 更多