C#Winforms:禁用跟踪已绑定的EF对象,以避免上下文更新,直到按下“更新”为止

时间:2018-11-09 18:37:40

标签: c# winforms entity-framework

因此,我有一个带有绑定到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(),但尝试在Update上重新附加时发生错误:它是重复的,因为该对象的实例是以列表形式创建的。

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

编辑:

现在,在使用FrmCategoryView构造函数中的ID来获取类别之后,我尝试将类别的实体状态设置为“分离”,

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

我第一次关闭视图窗体时不起作用:网格行由于上下文更新而更新,即使category条目设置为“分离”也是如此。但是下一次,它没有发生。它像应该工作一样工作。在重新附加并保存“更新”按钮上的更改之前,上下文不会跟踪我对category所做的更改。

2 个答案:

答案 0 :(得分:1)

这里的问题是您使用过ShowDialog。阅读下面链接的备注部分的第二点

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

在这种情况下,表单不会被处理。

  

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

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

答案 1 :(得分:0)

我找到了解决方法。

回顾一下,这是我对原始代码所做的更改:

  • AsNoTracking()中填充网格时添加了FrmCategoryList
  • 使用category构造函数中的ID从上下文中分离FrmCategoryView
  • 重新附加category,然后将更改保存在“更新”和“删除”上,并分别具有“已修改”和“已删除”状态。

经过一些测试,我没有发现任何问题。任何建议表示赞赏。