EF核心-一次性DbContext和Attach()-或-DbContext作为成员-或-断开连接的实体 [英] EF Core - Disposable DbContext and Attach() - or - DbContext as member - or - Disconnected Entities

查看:33
本文介绍了EF核心-一次性DbContext和Attach()-或-DbContext作为成员-或-断开连接的实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不确定如何将DbContext用于绑定到WPF DataGrid的实体吗?

I'm not sure about how to correctly use the DbContext for Entities bound to a WPF DataGrid?

对于在UserControl加载期间加载到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?

我使用DbContext作为成员变量,使用ObservableCollection作为Datagrids的数据源.因此,到目前为止一切都很好,无需在下面的代码中搜索错误.只是为了展示我到目前为止所做的.

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,当前从MVVM开始,并搜索如何正确集成EF Core.现在我已经读了好几次了:

BUT, currently starting with MVVM and search how to correctly integrate EF Core. Now I have read several times:

您的DbContext生命周期应限于您所使用的事务正在运行.

例如此处: c#实体框架:在存储库类内部正确使用DBContext类

因此考虑到这一点,并将保存代码更改为:

So taking this into account and changed the saving code to:

// 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作为成员变量使用,就像以前使用的那样...

Do I really need to loop over all entities, attach every single entity and check manually for deleted or added entities? I don't like to have a DbContext opened every time when CollectionChanged event appears. I can't believe it should be this complicated... So currently I would prefer to go with the DbContext as member variable as used before...


如果我正确无误地将未实现的断开连接的实体不打算在具有DB-Server连接的WPF应用程序中使用,则应在n层环境中使用它们.所以这不是要搜索的主题,对吗?
我需要断开连接的实体吗?
MSDN上的断开实体

推荐答案

侧面说明:

应该用于通知View有关模型的更改,分别是ViewModel.我不建议让View修改 ObservableCollection ,然后使用 CollectionChanged 反映ViewModel中的更改.尝试保持 ViewModel-> View通知流,而不是其他方向.*每个更改都在ViewModel中完成并反映在View中.

in MVVM ObservableCollection.CollectionChanged is supposed to inform View about changes in Model, resp ViewModel. I would not recommend to let View modify ObservableCollection and then use CollectionChanged to reflect the changes in ViewModel. Try to keep ViewModel -> View notification flow, not the other direction.* Every change is done in ViewModel and reflected in the View.

基本上拆分了应用程序逻辑和数据访问权限,这正是viewmodel的用途.

basically split your application logic and your data access, which is exactly what viewmodel is for.

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和更改跟踪模式的实现.

Second approach:

In the previevious example we have basically implemented our own primitive Unit of Work pattern. However, DbContext is already implementation of UoW and change tracking pattern.

我们将创建DBContext实例,但仅将其用于跟踪已添加/已删除的实体:

We will create instance of DBContext, but we will use it only for tracking Added/Removed entities:

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

这篇关于EF核心-一次性DbContext和Attach()-或-DbContext作为成员-或-断开连接的实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆