I'm not sure about how to correctly use the DbContext for Entities bound to a WPF DataGrid?
How do I correctly "re-attach" and save changes to the database for all the entities that were loaded to the datagrid during UserControl load?
I was using a DbContext as a member variable and ObservableCollection as DataSource for Datagrids. So everything was fine so far, no need to search for errors in the code below. Just to show what I have done so far.
// Old code - working perfectly as desired
private TestMenuDataContext _Db;
public ObservableCollection<Vendor> Vendors { get; set; }
private void ucGeneralSettings_Loaded(object sender, RoutedEventArgs e) {
//Do not load your data at design time.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {
_Db = new TestMenuDataContext();
_Db.Database.EnsureCreated();
Vendors = new ObservableCollection<Vendor>(_Db.Vendors);
Vendors.CollectionChanged += Vendors_CollectionChanged;
vendorDataGrid.ItemsSource = Vendors;
}
}
private void Vendors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
_Db.Vendors.AddRange(e.NewItems.Cast<Vendor>());
foreach (var vendor in e.NewItems.Cast<Vendor>()) {
vendor.TimeStamp = DateTime.Now;
vendor.UserName = Environment.UserName;
}
break;
case NotifyCollectionChangedAction.Remove:
_Db.Vendors.RemoveRange(e.OldItems.Cast<Vendor>());
break;
}
}
private void SaveSettingsButton_Click(object sender, RoutedEventArgs e) {
var queryDeletedUsedVendor = _Db.TestMenu.Where(t => !Vendors.Any(v => v.Name== t.Vendor));
if (queryDeletedUsedVendor.Any()) {
_AppManager.AddStatusMessage($"Saving settings not possible. Vendor {queryDeletedUsedVendor.FirstOrDefault().Vendor} deleted but it is in use in the Test Menu!", State.Error);
return;
}
try {
_Db.SaveChanges();
_AppManager.AddStatusMessage("Settings saved", State.Ok);
}
catch (Exception ex) {
_AppManager.AddStatusMessage($"Saving data failed {ex.Message}", State.Error);
}
// fire delegate event to inform MainWindow
onDatabaseUpdated?.Invoke(this);
}
private void ucGeneralSettings_Unloaded(object sender, RoutedEventArgs e) {
if (_Db != null)
_Db.Dispose();
}
BUT, currently starting with MVVM and search how to correctly integrate EF Core. Now I have read several times:
Your DbContext lifetime should be limited to the transaction you are running.
E.g. here: c# entity framework: correct use of DBContext class inside your repository class
的不同行为因此考虑到这一点并将保存代码更改为:
// new code
using (TestMenuDataContext db = new TestMenuDataContext())
{
foreach (Vendor v in Vendors) {
var test = db.Vendors.Attach(v);
bool isAlreadyInside = db.Vendors.Any(v2 => v2.Id == v.Id);
if (!isAlreadyInside)
db.Vendors.Add(v);
}
db.SaveChanges();
我是否真的需要遍历所有实体,附加每个实体并手动检查已删除或添加的实体?每次出现CollectionChanged事件时,我都不想打开DbContext。我不能相信它应该是这么复杂......所以目前我更愿意将DbContext作为成员变量使用,如前所用...
如果我正确搜索,那么未实现的断开连接的实体并不打算在具有DB-Server连接的WPF应用程序中使用,它们应该用于n层环境。所以这不是搜索的主题,对吗?
Do I need disconnected entities?
Disconnected Entities on MSDN
答案 0 :(得分:1)
每次
DbContext
事件出现时,我都不想打开CollectionChanged
。
然后不要。在视图模型中创建一个TestMenuDataContext
,并像以前一样使用此模型。
所以目前我更愿意使用之前使用的
DbContext
作为成员变量。
没有什么可以阻止你这样做,是吗?显然,在这种情况下,您确实希望每个视图模型实例都有一个TestMenuDataContext
。只需在视图模型中创建一次TestMenuDataContext
,例如在其构造函数中,并在CollectionChanged
事件处理程序中使用此项。或者在保存方法中创建上下文。
DbContext
的最佳生命周期肯定会根据您的要求而有所不同。通常,您应该使用短期上下文,但在这种情况下,您似乎希望(并且应该使用)对DataGrid
中的实体对象所做的所有更改使用相同的上下文。
另一个选项当然是在按下保存按钮时创建上下文并附加实体(而不是每次修改内存中的集合时)。
答案 1 :(得分:1)
旁注:
MVVMObservableCollection.CollectionChanged
中的应该通知View有关Model中的更改,resp ViewModel。我不建议让View修改ObservableCollection
,然后使用CollectionChanged
来反映ViewModel中的更改。尝试保持 ViewModel - &gt;查看通知流程,而不是其他方向。*每次更改都在ViewModel中完成并反映在视图中。
基本上将您的应用程序逻辑和数据访问分开,这正是viewmodel的用途。
public class YourPageViewModel
{
private readonly ObservableCollection<VendorItemVm> _deletedVendors = new ObservableCollection<VendorItemVm>();
public List<VendorItemVm> Vendors { get; } = new List<VendorItemVm>();
void Add()
{
Vendors.Add(new VendorItemVm
{
IsNew = true,
Id = new Guid(),
UserName = "New Vendor",
});
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_deletedVendors.Add(vendor);
}
async Task Load()
{
using(var db = new DbContext())
{
var vendors = db.Vendors.AsNoTracking().ToList();
foreach(var entity in vendors)
{
Vendors.Add(new VendorItemVm
{
Id = entity.Id,
Name = entity.Name,
});
}
}
}
async Task Save()
{
using (var db = new DbContext())
{
//convert viewmodels to entities
var newVendorsEntities = Vendors
.Where(v => v.IsNew)
.Select(v => new Vendor
{
Id = v.Id,
UserName = v.UserName,
TimeSpan = DateTime.Now,
})
.ToArray();
//add new entities
foreach (var vm in Vendors.Where(v => v.IsNew))
{
var entity = new Vendor
{
Id = vm.Id,
UserName = vm.UserName,
TimeSpan = DateTime.Now,
};
db.Vendors.Add(vendor);
}
//delete removed entities:
foreach(var vm in _deletedVendors)
{
var entity = new Vendor { Id = vm.Id };
db.Vendors.Attach(entity);
db.Ventors.Remove(entity);
db.Vendors.Add(vendor);
}
await db.SaveChangesAsync();
//reset change tracking
foreach (var vm in Vendors) vm.IsNew = false;
_deletedVendors.Clear();
}
}
}
在一个普遍的例子中,我们基本上实现了我们自己的原始工作单元模式。但是,DbContext已经实现了UoW并改变了跟踪模式。
我们将创建DBContext的实例,但我们仅将其用于跟踪已添加/删除的实体:
public class YourPageViewModel
{
MyDbContext _myUoW;
public ObservableCollection<Vendor> Vendors { get; } = new ObservableCollection<Vendor>();
void Add()
{
var entity = new Vendor
{
Id = new Guid(),
UserName = "New Vendor",
};
Vendors.Add(entity)
_myUoW.Vendors.Add(entity);
}
void Remove(VendorItemVm vendor)
{
Vendors.Remove(vendor);
_myUoW.Vendors.Attach(entity);
_myUoW.Vendors.Add(entity);
}
async Task Load()
{
using(var db = new MyDbContext())
{
Vendors = db.Vendors.AsNoTracking.ToList();
foreach(var entity in vendors) Vendors.Add(entity);
}
_myUoW = new MyDbContext();
//if you want to track also changes to each vendor entity, use _myUoW to select the entities, so they will be tracked.
//In that case you don't need to attach it to remove
}
async Task Save()
{
//add new entities and delete removed entities
_myUoW.SaveChanges();
//reset change tracking
_myUoW.Dispose();
_myUoW = new MyDbContext();
}
}