首页 > 解决方案 > C# Winforms:禁用跟踪绑定的 EF 对象以避免上下文更新,直到按下“更新”

问题描述

因此,我有一个带有绑定到 a 的类别列表的表单DataGridView,当我双击其中一行时,会打开一个包含该类别详细信息的新表单,并且还允许用户编辑或删除所选类别.

这是两种形式的代码:

FrmCategoryList.cs

public partial class FrmCategoryList : Form
{
    private readonly DbContext dbContext;

    public FrmCategoryList()
    {
        InitializeComponent();
        dbContext = new DbContext();
    }

    private void FrmCategoryList_Load(object sender, EventArgs e)
    {
        FillGrid();
    }

    private void txtSearch_TextChanged(object sender, EventArgs e)
    {
        FillGrid();
    }

    private void dgvCategories_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
    {
        var grid = (DataGridView)sender;
        if (e.RowIndex >= 0)
        {
            var category = (Category)grid.Rows[e.RowIndex].DataBoundItem;
            var frmCategoryView = new FrmCategoryView(category.Id, dbContext);

            if (frmCategoryView.ShowDialog() == DialogResult.OK)
                FillGrid();
        }
    }

    private void btnNew_Click(object sender, EventArgs e)
    {
        var frmCategoryView = new FrmCategoryView(0, dbContext);

        if (frmCategoryView.ShowDialog() == DialogResult.OK)
            FillGrid();
    }

    private void btnClose_Click(object sender, EventArgs e)
    {
        Close();
    }

    private void FillGrid()
    {
        categoryBinding.DataSource = dbContext.Categories.Where(p => p.Description.StartsWith(txtSearch.Text)).ToList();
    }

    private void FrmCategoryList_FormClosing(object sender, FormClosingEventArgs e)
    {
        dbContext?.Dispose();
    }
}

FrmCategoryView.cs

public partial class FrmCategoryView : Form
{
    private readonly DbContext dbContext;
    private Category category;

    public FrmCategoryView(int categoryId, DbContext dbContext)
    {
        InitializeComponent();
        this.dbContext = dbContext;

        if (categoryId == 0)
        {
            category = new Category();
            btnSave.Visible = true;
        }
        else
        {
            category = dbContexto.Categories.Include(c => c.Products).FirstOrDefault(c => c.Id == categoryId);
            btnUpdate.Visible = true;
            btnDelete.Visible = true;
        }
    }

    private void FrmCategoryView_Load(object sender, EventArgs e)
    {
        txtDescription.DataBindings.Add("Text", category, "Description");
    }

    private void btnSave_Click(object sender, EventArgs e)
    {
        if (category.Validate(dbContext))
        {
            dbContext.Categories.Add(category);
            dbContext.SaveChanges();

            MessageBox.Show("Category added!", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
            DialogResult = DialogResult.OK;
            Close();
        }
    }

    private void btnUpdate_Click(object sender, EventArgs e)
    {
        if (category.Validate(dbContext))
        {
            dbContext.Categories.Attach(category);
            dbContext.Entry(category).State = EntityState.Modified;
            dbContext.SaveChanges();

            MessageBox.Show("Category updated!", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
            DialogResult = DialogResult.OK;
            Close();
        }
    }

    private void btnDelete_Click(object sender, EventArgs e)
    {
        if (MessageBox.Show("¿Are you sure to delete this category?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
        {
            if (category.Products.Count > 0)
            {
                MessageBox.Show("You cannot delete a category with dependencies.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            dbContext.Categories.Remove(category);
            dbContext.SaveChanges();

            MessageBox.Show("Category deleted!.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
            DialogResult = DialogResult.OK;
            Close();
        }
    }

    private void btnClose_Click(object sender, EventArgs e)
    {
        DialogResult = DialogResult.Cancel;
        Close();
    }
}

因此,当我编辑某个类别的某个字段,然后在不按更新按钮的情况下关闭表单时,问题就来了。在关闭之前不会释放上下文FrmCategoryList,并且类别对象从FrmCategoryView绑定到文本,因此更新上下文但不会将更改保存到数据库中。因此,如果我打开其他类别并实际更新它,则在调用时也会保存先前SaveChanges()的更改,因为上下文会跟踪用户“丢弃”的更改。

我该怎么做才能解决这个问题?我可以在 中重新初始化上下文FrmCategoryView,但是如果我实际保存某些内容,更改将不在FrmCategoryList上下文中,而且我不知道如何强制 EF 检查数据库,即使已经跟踪的对象也是如此。

此外,也许有一些方法可以将对象从上下文中分离出来,然后在按下更新按钮时重新附加它。我尝试使用 AsNoTracking() 但尝试重新附加更新时发生错误:它是重复的,因为该对象的实例是在列表表单中创建的。

对不起我的英语不好,希望你能理解!

编辑:

现在,我尝试使用FrmCategoryView构造函数中的 Id 将类别的实体状态设置为已分离,并使用:

dbContext.Entry(category).State = EntityState.Detached;

我第一次关闭视图表单时它不起作用:网格行得到更新,因为上下文已更新,即使category条目设置为分离。但接下来的一次,它没有发生。它的工作方式就像它应该工作一样。我所做的更改category不会被上下文跟踪,直到我重新附加它并在更新按钮上保存更改。

标签: c#winformsentity-framework

解决方案


这里的问题是你使用过ShowDialog. 阅读以下链接备注部分的第二点

https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.form.close?view=netframework-4.7.2

在这种情况下,不会释放表单。

您已经使用 ShowDialog 显示了表单。在这些情况下,您将需要手动调用 Dispose 以将表单的所有控件标记为垃圾回收。

或者,如果您想要模态对话框,则通过处理事件来清理上下文closing,如果您在关闭父窗体之前对其他控件没有问题。


推荐阅读