EF Core - Disposable DbContext and Attach() - or - DbContext as member - or - Disconnected Entities

时间:2017-08-24 12:46:11

标签: c# .net wpf datagrid entity-framework-core

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

2 个答案:

答案 0 :(得分:1)

  

每次DbContext事件出现时,我都不想打开CollectionChanged

然后不要。在视图模型中创建一个TestMenuDataContext,并像以前一样使用此模型。

  

所以目前我更愿意使用之前使用的DbContext作为成员变量。

没有什么可以阻止你这样做,是吗?显然,在这种情况下,您确实希望每个视图模型实例都有一个TestMenuDataContext。只需在视图模型中创建一次TestMenuDataContext,例如在其构造函数中,并在CollectionChanged事件处理程序中使用此项。或者在保存方法中创建上下文。

DbContext的最佳生命周期肯定会根据您的要求而有所不同。通常,您应该使用短期上下文,但在这种情况下,您似乎希望(并且应该使用)对DataGrid中的实体对象所做的所有更改使用相同的上下文。

另一个选项当然是在按下保存按钮时创建上下文并附加实体(而不是每次修改内存中的集合时)。

答案 1 :(得分:1)

旁注:

MVVM ObservableCollection.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();
    }
}