使用EF核心和c#数据绑定进行数据绑定时刷新数据
Posted
技术标签:
【中文标题】使用EF核心和c#数据绑定进行数据绑定时刷新数据【英文标题】:Refresh data when data binding using EF core and c# data binding 【发布时间】:2021-12-06 08:38:37 【问题描述】:我正在使用 VS 2019,带有 Win 表单和实体框架核心 5.0 的 c# 应用程序
在我的应用程序中,我有一个 System.Windows.Forms.DataGridView 用于显示和更新 mysql 数据库中的数据。 数据通过使用 System.Windows.Forms.BindingSource myBindingSource 绑定到 DataGridView 并使用绑定 EF 表数据
myDbContext.SomeEntities.Load();
myBindingSource.DataSource = myDbContext.SomeEntities.Local.ToBindingList();
这确实可以正确显示数据,一旦我更改网格中的一些数据并调用 myDbContext.SaveChanges(),它就会将数据保存到数据库中。
所以,只要应用程序独立运行,它就可以正常工作。
但是,我想要实现的是,每当我的应用程序之外的任何其他操作更改数据时,包含网格的表单都会刷新数据。因此,如果数据的任何更新发生在我的应用程序之外,我希望这些更改在打开的表单中立即可见,而无需用户关闭并重新打开包含 DataGridView 的表单。 我知道,当然,我需要触发这些变化。这可能是一个定时器或一个外部信号。现在它是一个计时器。
在计时器中我做了一个
foreach( var rec in (BindingList<SomeEntities>)this.DataSource)
DbContext.Entry(rec).Reload();
然后我做了一个
CurrencyManager cm = (CurrencyManager)((myDataGridView).BindingContext)[(ctrl as DataGridView).DataSource];
if (cm != null) cm.Refresh();
这适用于现有记录的外部更新。 但是,如果插入或删除一条记录,它就会失败。外部插入时,新记录在现有的 BindingList 中根本不知道,因此不会刷新;当一条记录被外部删除时,重新加载失败(因为它不再存在于数据库中)。 对于正在发生的事情,两者都是可以理解的。
不仅刷新现有实体而且刷新集合 myDbContext.SomeEntities 的内容的正确方法是什么
在寻找答案时,我经常阅读“使用 DbContext 的短生命周期”。可以理解,但我确实需要 DbContext 才能调用 myDbContext.SaveChanges() 以保存在网格中所做的任何更改。我吗?还是有其他方法?如果 DbContext 仅在加载网格期间使用,我如何使用常规数据绑定将其用作网格的数据源?
有了 EntityFramework 6
_myObjectContext.RefreshAsync(RefreshMode.StoreWins, GetAll())
不知道这是否会有所帮助,因为我没有尝试使用 EntityFramework 6,但无论如何,在 EF 核心中没有与此等价的东西。那么有什么建议吗?
【问题讨论】:
【参考方案1】:我认为在这种情况下 - 当你的数据网格一直打开时(据我所知),这应该更多地手动完成。
例如,使用 MVVM,我将创建一个 ViewModel,其中包含一些可观察的视图项集合,例如:
假设这是您的数据库模型:
public class DbItem
public int Id get;set;
public string Name get;set;
现在,我将创建一些要在视图模型中使用的数据:
public class ItemData: INotifyPropertyChanged
public ItemData(DbItem item)
id = item.Id;
name = item.Name; //notice that I use here backup field
public bool Modified get; private set;
int id;
public int Id
get return id;
set
if(id != value)
id = value;
NotifyPropertyChanged();
string name;
public string Name
get return name;
set
if(name != value)
name = value;
NotifyPropertyChanged();
在这里,我使用了 INotifyPropertyChanged,但这一切都取决于您的需求。您可以将字段 Modified
更新为 true,或者每次记录更改时,只需在 db 中更新它(SQL UPDATE/INSERT)
现在在我的 ViewModel 中我会这样做:
public class ViewModel
public ObservableCollection<ItemData> DataSource get; set;
我怀疑您是否可以像在 WPF 中一样在 WinForms 中使用 ObservableCollection,所以我认为您可以创建一些绑定集合。
无论如何,现在当您从数据库中读取数据时,您应该将它们转换为您的项目:
public class ViewModel
public void ReadData()
DataSource.Clear();
List<DbItem> dbItems = service.GetDataFromDatabaseWithNoTracking();
foreach(var item in dbItems)
DataSource.Add(new ItemData(item));
现在,当您需要更新某些内容时,只需:
public class ViewModel
public void UpdateData(ItemData data)
//if data.Modified...
DbItem db = new DbItem();
db.Id = data.Id;
db.Name = data.Name;
service.UpdateItem(db);
在 EF 中:
public void UpdateItem(DbItem item)
var entry = dbContext.Entry(item);
dbContext.Save();
归根结底,您不会跟踪数据库中的记录。您应该手动执行此操作。
你觉得这个解决方案怎么样?
【讨论】:
嗯,实际上这就是我开始迁移到实体框架时的想法。在新应用程序的前身中,我正是这样做的,将所有数据置于手动控制之下。 (在旧程序中,它使用直接 SQL 命令来读取和修改数据)。在我相当于您的 ReadData() 中,我什至没有清除和重新创建项目,而是更新了现有的项目。所有需要维护的大量代码。所以当我开始构建它时,我认为实体框架应该让我免于手动编码,似乎我错了。 嗯,您的需求比 EF 开箱即用更具体。所以你必须做更多的工作。【参考方案2】:我不确定这是否是一个不错的界面。假设操作员正在愉快地编辑一行,突然你决定重新加载表格:他的所有更改都丢失了? 如果操作员只是将一个值从 4 更改为 5,而其他人只是将相同的值从 4 更改为 3,我们应该保留哪一个呢?如果其他人决定删除一行怎么办,因为他认为
所以我建议不要进行自动刷新,为此添加一个按钮。类似于浏览器中的重新加载按钮。添加 F5 按钮开始刷新,您的界面与大多数可能显示过时数据的 Windows 应用程序兼容。
但是,这有一个缺点,如果操作员编辑了一个值,其他人也编辑了,甚至删除了,你应该决定做什么。我的建议是:询问接线员:
如果操作员编辑了其他人也编辑的行:我们应该保留哪一行? 如果操作员编辑了其他人删除的行? 如果操作员删除了其他人刚刚更改的行?假设您的 datagridview 显示 Products
:
class Product
public int Id get; set;
public string Name get; set;
public decimal Price get; set;
public int Stock get; set;
...
使用 Visual Studio 设计器,您添加了一个 DataGridView 和一些列。在您的构造函数中,您可以将属性分配给列:
public MyForm() : Form
InitializeComponents()
this.columnId.DataPropertyName = nameof(Product.Id);
this.columnName.DataPropertyName = nameof(Product.Name);
this.columnPrice.DataPropertyName = nameof(Product.Price);
...
您还需要一个方法来获取必须从数据库中显示的产品,然后再次重新获取它们,以查看哪些产品已更改。当然,您隐藏它们来自数据库。
IEnumerable<Product> FetchProductsToDisplay()
... // Fetch the data from the database; out-of-scope of this question
要显示获取的产品,请使用 DataSource 和 BindingList:
BindingList<Product> DisplayedProducts
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
最初您展示的是产品:
void OnFormLoading(object sender, ...)
this.DisplayedProducts = this.FetchProductsToDisplay()
所以现在操作员可以愉快地编辑现有产品,可能添加一些新产品,或者删除一些产品。过了一会儿,他想用其他人输入的数据刷新产品:
private void OnButtonRefresh_Clicked(object sender, ...)
this.UpdateProducts();
private void UpdateProducts()
IEnumerable<Product> dbProducts = this.FetchProductsToDisplay();
IEnumerable<Product> editedProducts = this.DisplayedProducts();
我们必须将数据库中的产品与 DataGridView 中的产品进行比较。按 Id 匹配产品:相同的 Id,期望相同的产品。
Id 为零,因此仅在editedProducts 中,已由操作员添加,但尚未添加到数据库中。 非零Id仅在editedProducts中:已被其他人删除 Id 在 dbProducts 和 editeProducts 中都有,但值不相等:要么由操作员编辑,要么由其他人编辑一个困难的:
id 仅在 dbProducts 中:已被其他人添加,或已被操作员删除。为了能够向操作员提出正确的问题,我们似乎还需要在操作员开始编辑之前显示的产品。
private IEnumerable<Product> OriginalProducts => ...
所以现在我们能够检测操作员和/或在数据库中添加/删除/更改了哪些产品:
Id in originalProducts editedProducts dbProducts
yes yes yes compare values to detect edits
yes yes no someone else deleted
yes no yes operator deleted.
yes no no both operator and someone else deleted
no no yes someone else added
no yes no operator added
no yes yes both operator and someone else added
检测变化的过程:
private void DetectChanges(IEnumerable<Product> originalProducts,
IEnumerable<Product> editedProducts,
IEnumerable<Product> dbProducts)
// do a full outer join on these three sequences:
var originalDictionary = originalProducts.ToDictionary(product => product.Id);
var dbDictionary = dbProducts.ToDictionary(product => product.Id);
// some Ids in editedProducts have value zero:
var addedProducts = editedProducts.Where(product => product.Id == 0);
var editedDictionary = editedProducts.Where(product => product.Id != 0)
.ToDictionary(product => product.Id);
var allUsedIds = originalDictionary.Keys
.Concat(dbDictionary.Keys)
.Distinct();
注意:所有 ID 为 != 0 的已编辑产品在上次获取时已存在于数据库中,因此它们的 ID 已在 originalDictionary 中。我使用 Distinct 删除重复的 Ids
foreach (int id in allUsedIds)
bool idInDb = dbDictionary.TryGetValue(id, out Product dbValue)
bool idInOriginal = originalDictionary.TryGetValue(id, out Product originalValue);
bool idInEdited = editedDictionary.TryGetValue(id, out Product editedValue);
使用上面的表格,以及值 idInDb / idInOriginal / idInEdited 来确定该值是否已添加或删除/更改。
有时只需添加/编辑/更改 DataGridView 中的值就足够了;有时您必须询问接线员。
建议:
其他人所做的更改:
如果由其他人添加:只需将其添加到 DataGridView 中 如果被其他人删除,而不是由操作员编辑:只需从 DataGridView 中删除 如果被其他人删除,由操作员编辑:询问操作员该怎么做操作员所做的更改:
如果由操作员添加(Id == 0):插入数据库中
如果被操作员删除,则将其从数据库中删除
如果由操作员而非其他人编辑:更新数据库
操作员和其他人所做的更改:询问操作员要保留哪一个。更新数据库或 DataGridView。
检测产品更改:使用 IEqualityComparer
要检测变化,您需要一个按值比较的相等比较器:
public class ProductComparer : EqualityComparer<Product>
public static IEqualityComparer<Product> ByValue get = new ProductComparer();
public override bool Equals(Product x, Product y)
if (x == null) return y == null; // true if both null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true; // same object
if (x.GetType() != y.GetType()) return false; // different types
return x.Id == y.Id
&& x.Name == y.Name // or use StringComparer.CurrentCultureIgnoreCase
&& x.Price == y.Price
&& ...
public override int GetHashCode(Product x)
if (x == null) return 87742398;
// for fast hash code: use Id only.
// almost all products are not edited, so with same Id have same values
return x.Id.GetHashCode();
用法: IEqualityComparer productValueComparer = ProductComparer.ByValue;
检测出哪些产品在哪个词典中后:
if (idInDb && idInEdited && )
// one of the Products in the DataGridView already exists in the database
// did the operator edit it?
bool operatorChange = !productValueComparer.Equal(originalValue, editedValue);
bool dbChange = !productValueComparer.Equal(originalValue, dbValue);
if (!productValueComparer.Equal(operatorValue, dbValue);)
// operator edited the Product; someone change it in the database
// ask operator which value to keep
...
else
// operator edited the Product; the same value is already in the database
// nothing to do.
所以对于每个Id,检查它是否已经在数据库中,检查它是否还在数据库中,并检查操作员是否将它保存在dataGridView中。还要检查哪些值已更改,以检测您是否需要添加/删除/更新数据库,或者 DataGridView,或者您可能什么都不做。
【讨论】:
以上是关于使用EF核心和c#数据绑定进行数据绑定时刷新数据的主要内容,如果未能解决你的问题,请参考以下文章
如何用c#对datagridview刷新, 是否重新绑定一下, 怎样重新绑定。
C#中datagridview如何绑定ArrayList集合?
C# WPF 数据绑定DataContext;Window_Loaded时进行过数据绑定,指定DataContext;触发另一事件?