因此,我有一个表单,其中包含绑定到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将类别的Entity State设置为Detached,并使用:
dbContext.Entry(category).State = EntityState.Detached;
当我第一次关闭视图窗体时,它不起作用:网格行会更新,因为上下文会更新,即使category
条目设置为Detached也是如此。但接下来的一次,它没有发生。它像应该工作的那样工作。我对category
所做的更改不会被上下文跟踪,直到我重新附加它并将更改保存在"更新"按钮上。
这里的问题是您使用了ShowDialog
。阅读下面链接备注部分的第二点
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.close?view=netframework-4.7.2
在这种情况下,表单不会被处理。
您已经使用ShowDialog显示了表单。在这些情况下,您将需要手动调用Dispose来标记表单的所有控件垃圾收集。
或者,如果在关闭父窗体之前其他控件没有问题,则根据您想要的模式对话框,通过处理closing
事件来清理上下文。
我找到了一个解决方案。
概括一下,这是我对原始代码所做的更改:
- 在
FrmCategoryList
中填充网格时添加了AsNoTracking()
- 使用
FrmCategoryView
构造函数中的id获取category
后,将其从上下文中分离 - 在"更新"one_answers"删除"上保存更改之前,请重新附加
category
,状态分别为"已修改"one_answers"已删除">
经过一些测试,我没有发现任何问题。欢迎提出任何建议。