首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >通过GridView绑定一个奉承模式EF实体,并允许与存储库同步

问通过GridView绑定一个奉承模式EF实体,并允许与存储库同步
EN

Stack Overflow用户
提问于 2014-07-04 21:21:15
回答 2查看 1K关注 0票数 0

我第一次在复杂的网格数据库环境中使用了实体框架,我需要一些策略来完成这个任务。我获得了由“each反向代码优先”(映射相同)自动生成的POCO类,对于每个类,还有一个存储库,用于查询或执行简单的CRUD操作:

代码语言:javascript
复制
    public class Order
    {
        public int IdOrder { get; set; }
        public int IdCustomer { get; set; }
        public int IdOrderType {get; set;}
        public string ChOrder { get; set; }
        public decimal Total { get; set; }

        public Customer Customer { get; set; }
        public OrderType OrderType {get; set;}
    }

    public class OrderType 
    {
        public int IdOrderType {get; set;}
        public string ChOrderType {get; set;}
        public string Description { get; set; }
    }

    public class Customer
    {
        public int IdCustomer { get; set; }
        public string ChCustomer { get; set;}
        public string Name { get; set; }
        public string City { get; set; }
        public CustomerType CustomerType { get; set;}
    }

    public class CustomerType
    {
        public int IdCustomerType { get; set; }
        public string ChCustomerType { get; set; }
    }

现在,我想查询OrderRepository并在DataGridView中显示所有结果(在实际情况下,它是DevExpress XtraGrid)。此DataGridView必须显示“直接”顺序信息(ChOrder,总计),也必须显示“间接”信息,例如使用导航属性(ex )可以到达的信息。Customer.Name,Customer.City,Customer.CustomerType.ChCustomerType)。我想要的是这样的东西:

​

​

在“只读DataGridView”场景中,这个任务相当简单。您只需创建一个linq查询,该查询可以对存储库的结果集进行修饰,并将其绑定到网格,如下所示:

代码语言:javascript
复制
this.OrderRepository.AsQueryable().Select(x => new { .ChOrder = x.ChOrder, 
                             .Total = x.Total, 
                             .ChOrderType = x.OrderType.ChOrderType, 
                             .ChCustomer = x.ChCustomer, 
                             .Name = x.Customer.Name, 
                             .City = x.Customer.City, 
                             .CustomerType = x.Customer.CustomerType}).ToList();

但是网格必须是可编辑的。特别是,我想添加新的订单,修改ChOrderType (以及它的描述),修改ChCustomer (以及相应的名称、城市和类型)和删除订单。我无法绑定LINQ结果,因为:

  1. LINQ匿名类型结果是不可变的(因此网格将是只读的)
  2. LINQ生成一个列表,其中项目可以更改(用户可以删除项目,添加新项目,修改等等)而且,由于它是一个列表,它无法跟踪已删除、添加和修改的轮式项(即使我使用了更合适的BindingList或ObservableCollection)。因此,我不知道哪些实体删除、更新或插入。
  3. LINQ结果项不实现INotifyPropertyChanged,因此,如果我更改例如"ChCustomer“它的相对数据(名称,City .)不会在屏幕上更新。

由于这些问题,我希望以这样的方式直接绑定POCO OrderClass:

代码语言:javascript
复制
this.OrderRepository.AsQueryable().Select()

并将“间接”字段"DataPropertyName“设置为:

列"Name“-> Customer.Name “城市”栏目-> Customer.City

但同样,它不适合,因为:

  1. 我的POCO类不实现INotifyPropertyChanged,因此,如果我更改例如"ChCustomer“它的相关数据(名称,City .)不会在屏幕上更新。

  1. 例如,当我编辑ChCustomer时,显然数据库不知道如何根据用户插入的ChCustomer从其存储库中获取客户实例,并将其分配给当前的Order " Customer“属性。

  1. 有时,可编辑字段(如ChCustomers)是复合键的一部分,实际上在EF中不允许编辑实体的复合键。这带来了更多的问题。

因此,在故事的结尾,当EF涉及到数据绑定时,这是一个很难管理的任务(在我们的软件中有上千个这样的例子)。

我想知道是否有一个好的策略来完成这项任务。

我讨论了如何通过使用ViewModel (也实现了INotifyPropertyChanged)对象来绑定到gridView来处理这个问题:

代码语言:javascript
复制
public class OrderViewModel : INotifyPropertyChanged{

        public event PropertyChangedEventHandler PropertyChanged;

        //Order info
        public string ChOrder { get; set; }
        public decimal Total {get; set;}
        public string ChOrderType {get; set;}
        public string Description { get; set; }

        //Customer info
        public string ChCustomer { get; set; }
        public string Name { get; set;}
        public string City { get; set;}
        public string ChCustomerType {get; set;}
    }

它工作得很好,但我不知道如何与存储库同步数据。特别是如何跟踪已删除的项目并使用存储库删除它们?如何跟踪添加的项目?编辑改变了吗?和also...how来设置来自导航属性(如名称、城市等)的所有属性?当我调用UnitOfWork的UnitOfWork()时,我希望将所有的字符正确地保存到DB中。

抱歉,如果我长篇大论的话,但我尽力做到最清楚。提前谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-07-21 17:21:08

为了解决这个问题,我在稍微复杂的结构中使用了@User978139提出的ViewModel解决方案。特别是,我创建了包装订单模型的OrderViewModel,该模型的CustomerId和EmployeeId属性具有可读性和可写性。请注意,在其设置程序中,我添加了一些代码,以强制客户和雇员属性更改,并引发相应的PropertyChanged()事件。这是代码:

代码语言:javascript
复制
 public class OrderViewModel : IEntityViewModel, IDataErrorInfo
        {
            public event PropertyChangedEventHandler PropertyChanged;

            internal Order _order;
            INorthwindDbContext _ctx;

            public int OrderId
            {
                get { return _order.OrderId; }
            }
            public DateTime? OrderDate
            {
                get { return _order.OrderDate; }
                set
                {
                    _order.OrderDate = value;
                    RaisePropertyChanged("OrderDate");
                }
            }
            public string ShipName
            {
                get { return _order.ShipName; }
                set
                {
                    _order.ShipName = value;
                    RaisePropertyChanged("ShipName");
                }
            }
            public string ShipAddress
            {
                get { return _order.ShipAddress; }
                set
                {
                    _order.ShipAddress = value;
                    RaisePropertyChanged("ShipAddress");
                }
            }
            public string ShipCity
            {
                get
                {
                    return _order.ShipCity;
                }
                set
                {
                    _order.ShipCity = value;
                    RaisePropertyChanged("ShipCity");
                    ;
                }
            }

            public string CustomerId
            {
                get { return _order.CustomerId; }
                set
                {
                    _order.CustomerId = value;
                    var customer = this._ctx.Customers.Find(_order.CustomerId);
                    _order.Customer = customer;
                    RaisePropertyChanged("CustomerId");
                    RaisePropertyChanged("CompanyName");
                    RaisePropertyChanged("ContactName");
                }
            }
            public string CompanyName
            {
                get { return _order.Customer != null? _order.Customer.CompanyName : string.Empty; }
            }
            public string ContactName
            {
                get { return _order.Customer != null ? _order.Customer.ContactName : string.Empty; }
            }

            public int? EmployeeId
            {
                get { return _order.EmployeeId; }
                set
                {
                    _order.EmployeeId = value;
                    var employee = this._ctx.Employees.Find(_order.EmployeeId);
                    _order.Employee = employee;
                    RaisePropertyChanged("EmployeeId");
                    RaisePropertyChanged("LastName");
                    RaisePropertyChanged("FirstName");
                    RaisePropertyChanged("Title");
                }
            }
            public string LastName
            {
                get { return _order.Employee!=null? _order.Employee.LastName : string.Empty; }
            }
            public string FirstName
            {
                get { return _order.Employee!=null? _order.Employee.FirstName : string.Empty;  }
            }
            public string Title
            {
                get { return _order.Employee!=null? _order.Employee.Title : string.Empty;  }
            }

            public OrderViewModel(INorthwindDbContext ctx)
            {
                this.Init(new Order(), ctx);
            }

            public OrderViewModel(Order order, INorthwindDbContext ctx)
            {
                this.Init(order, ctx);
            }

            private void Init(Order order, INorthwindDbContext ctx)
            {
                this._order = order;
                this._ctx = ctx;
            }

            private void RaisePropertyChanged(string propname)
            {
                if (PropertyChanged != null)
                    PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propname));
            }

            public void NotifyChanges()
            {
                foreach (var prop in typeof(OrderViewModel).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead))
                    RaisePropertyChanged(prop.Name);
            }



            public string Error
            {
                get { return null; }
            }

            public string this[string columnName]
            {
                get {
                    switch (columnName)
                    {
                        case "CustomerId":
                            if (_ctx.Customers.Find(CustomerId)==null)
                                return "Invalid customer";
                            break;

                        case "EmployeeId":
                            if (_ctx.Employees.Find(EmployeeId)==null)
                                return "Invalid employee";
                            break;


                    }
                    return null;
                }
            }

        } 

然后,我用自定义的OrderRepository实现(BindingViewModelList)包装了由BindingList (内存中加载和添加的实体返回)公开的"Local“属性,该属性用于网格绑定,它与内部repository.Local属性(即ObservableCollection<>)同步,反之亦然。因此,如果在ObservableColleciton中添加内容,您也会看到BindingList中的更改,否则,如果向BindingList添加某些内容(例如,使用绑定到的网格),更改也会反映到ObservableCollection中。这样,我就不必跟踪ViewModels对象的状态,并手动地将更改反映到存储库中,这是一项非常痛苦的工作。使用此解决方案,就像存储库的Local直接绑定到我用于绑定的绑定列表一样。从网格中添加和删除记录包括从BindingList和Local中删除实体。因此,如果我调用SaveChanges(),所有的更改都会在没有显式同步的情况下持久化到DB。

我希望有一个更好的清洁解决方案来将模型集合映射到ViewModels集合,但建议的解决方案工作得很好。这是我的BindingViewModelList的代码:

代码语言:javascript
复制
public class BindingViewModelList<TViewModel, TModel> : BindingList<TViewModel>
{
    private readonly ObservableCollection<TModel> _source;
    private readonly Func<TModel> _modelFactory;
    private readonly Func<TModel, TViewModel> _viewModelFactory;
    private readonly Func<TViewModel, TModel> _getWrappedModel;

    private bool isSync = false;

    public BindingViewModelList(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory, Func<TModel> modelFactory, Func<TViewModel, TModel> getWrappedModel)
        : base(source.Select(model => viewModelFactory(model)).ToList())
    {
        Contract.Requires(source != null);
        Contract.Requires(viewModelFactory != null);

        this._source = source;
        this._modelFactory = modelFactory;
        this._viewModelFactory = viewModelFactory;
        this._getWrappedModel = getWrappedModel;

        this._source.CollectionChanged += OnSourceCollectionChanged;
        this.AddingNew += BindingViewModelList_AddingNew;
    }


    protected virtual TModel CreateModel()
    {
        return _modelFactory.Invoke(); 
    }

    protected virtual TViewModel CreateViewModel(TModel model)
    {
        return _viewModelFactory(model);
    }


    private void BeginSync()
    {
        this.isSync = true;
    }

    private void EndSync()
    {
        this.isSync = false;
    }


    void BindingViewModelList_AddingNew(object sender, AddingNewEventArgs e)
    {
        e.NewObject = CreateViewModel(CreateModel());
    }


    protected override void OnListChanged(ListChangedEventArgs e)
    {
        if (!this.isSync)
        {

            if (e.NewIndex >= 0 && e.NewIndex < this.Count)
            {
                bool ok = true;
                TViewModel item = default(TViewModel);
                try
                {
                    item = this[e.NewIndex];
                }
                catch (IndexOutOfRangeException ex)
                {
                    ok = false;
                }

                if (ok)
                {
                    switch (e.ListChangedType)
                    {
                        case ListChangedType.ItemMoved:
                            BeginSync();
                            this._source.Move(e.OldIndex, e.NewIndex);
                            EndSync(); 
                            break;

                        case ListChangedType.ItemAdded:
                            TModel modelAdded = _getWrappedModel(item);
                            BeginSync();
                            this._source.Add(modelAdded);
                            EndSync();
                            break;

                        case ListChangedType.ItemChanged:
                            //TModel modelChanged = _getWrappedModel(item);
                            //BeginSync();
                            //this._source[e.NewIndex] = modelChanged; 
                            //EndSync(); 
                            break;

                        case ListChangedType.Reset:
                            BeginSync();
                            this._source.Clear();
                            for (int i = 0; i < this.Count; i++)
                            {
                                TModel model = _getWrappedModel(this[i]);
                                this._source.Add(model);
                            }
                            EndSync();
                            break;
                    }
                }
            }
        }


        base.OnListChanged(e);
    }

    protected override void RemoveItem(int index)
    {
        if (!isSync) {
            TModel model = _getWrappedModel(this[index]);
            BeginSync();
            this._source.Remove(model);
            EndSync();
        }

        base.RemoveItem(index);
    }


    private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (isSync) return;

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                BeginSync();
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                }
                EndSync();
                break;

            case NotifyCollectionChangedAction.Move:
                if (e.OldItems.Count == 1)
                {
                    BeginSync();
                    this.Swap(e.OldStartingIndex, e.NewStartingIndex);
                    EndSync();
                }
                else
                {
                    List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList();
                    BeginSync();
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAt(e.OldStartingIndex);

                    for (int i = 0; i < items.Count; i++)
                        this.Insert(e.NewStartingIndex + i, items[i]);
                    EndSync();
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                BeginSync();
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);
                EndSync();
                break;


            case NotifyCollectionChangedAction.Replace:
                // remove
                BeginSync();
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);

                // add
                for (int i = 0; i < e.NewItems.Count; i++)
                    this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                EndSync();
                break;

            case NotifyCollectionChangedAction.Reset:
                BeginSync(); 
                Clear();
                for (int i = 0; i < e.NewItems.Count; i++)
                    this.Add(CreateViewModel((TModel)e.NewItems[i]));
                EndSync();
                break;

            default:
                break;
        }
    }



    public void Swap(int first, int second)
    {
        TViewModel temp = this[first];
        this[first] = this[second];
        this[second] = temp;
    }
}

您只需提供ViewModel类型、模型类型、模型工厂方法、ViewModel工厂方法和指定如何从viewmodel对象获取包装模型的方法。这就是一个例子:

代码语言:javascript
复制
Func<Order> modelCreator = () => new Order();
Func<Order, OrderViewModel> viewModelCreator = (model => new OrderViewModel(model, _ctx));
Func<OrderViewModel, Order> modelGetter = (viewModel => viewModel._order);
var _viewModelCollection = new BindingViewModelList<OrderViewModel, Order>(_ctx.Orders.Local, viewModelCreator, modelCreator, modelGetter);

这不是一个相当简单的解决方案,但正如我所说的,它工作得很好,所以我将这个线程标记为已回答。希望有人能找到更好的解决办法。

票数 1
EN

Stack Overflow用户

发布于 2014-07-05 08:51:29

您可以使用ViewModel方法将表示逻辑与视图粘合在一起。您不会使用它来表示绑定到数据网格的示例中的单个记录,而且由于支持双向绑定,通常在WPF应用程序中使用它。

使用扁平化的方法来表示多个对象可能不是最好的主意,因为客户可以有多个订单,因此,当您按照问题中的建议修改客户的详细信息时,您将需要强制使用同一个客户的PropertyChanged到其他记录。您可能想要实现的是应用程序中的主细节网格视图,您可以在其中展开客户行来查看他们的订单。

如果您想继续使用扁平结构的方法,您应该创建一个包装类来包含从存储库加载的实体。然后,您将公开包装类上的属性,引用来自基础实体的值属性。示例:

代码语言:javascript
复制
public class BindableRecord : INotifyPropertyChanged
{
    Order _OrderObject;
    internal Order OrderObject
    {
        get { return _OrderObject; }
        set
        {
            _OrderObject = value;
            _OrderObject.PropertyChanged += OrderObject_PropertyChanged;
        }
    }

    void OrderObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, e);
    }

    internal Customer CustomerObject { get; set; }

    internal CustomerType CustomerTypeObject { get; set; }

    public string CustomerName
    {
        get { return this.CustomerObject.Name; }
        set { this.CustomerObject.Name = value; }
    }

    public string CustomerType
    {
        get { return this.CustomerTypeObject.ChCustomerType; }
        set { this.CustomerTypeObject.ChCustomerType = value; }
    }

    public int OrderID
    {
        get { return this.OrderObject.IdOrder; }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

实体应该从INotifyPropertyChanged派生,因此您可以向BindableRecord发送通知,然后将通知转发到DataGridView。从INotifyPropertyChanged派生实体的示例可以在这里获得:How to get property change notifications with EF 4.x DbContext generator

然后,您将拥有一个返回BindableRecord的存储库。

代码语言:javascript
复制
public class OrderRepository
{
    public IList<BindableRecord> Get()
    {
        using (EntityContext ctx = new EntityContext())
        {
            return (from c in ctx.Customers
                    join o in ctx.Orders on c.Order equals o
                    join ct in ctx.CustomerTypes on c.CustomerType equals ct
                    select new BindableRecord() { CustomerObject = c, CustomerTypeObject = ct, OrderObject = o }).ToList();
        }
    }

    public void Save(IEnumerable<BindableRecord> addOrUpdateEntities, IEnumerable<BindableRecord> deletedEntities)
    {
        using (EntityContext ctx = new EntityContext())
        {
            foreach (var entity in addOrUpdateEntities)
            {
                ctx.Entry(entity.CustomerTypeObject).State = entity.CustomerTypeObject.IdCustomerType == default(int) ? EntityState.Added : EntityState.Modified;
                ctx.Entry(entity.CustomerObject).State = entity.CustomerObject.IdCustomer == default(int) ? EntityState.Added : EntityState.Modified;
                ctx.Entry(entity.OrderObject).State = entity.OrderObject.IdOrder == default(int) ? EntityState.Added : EntityState.Modified;
            }
            foreach (var entity in deletedEntities)
            {
                ctx.Entry(entity.CustomerTypeObject).State = EntityState.Deleted;
                ctx.Entry(entity.CustomerObject).State = EntityState.Deleted;
                ctx.Entry(entity.OrderObject).State = EntityState.Deleted;
            }
            ctx.SaveChanges();
        }
    }
}

Save方法附加实体,然后根据它们是否已被修改、添加或删除来更新它们的状态。您必须维护两个列表,一个用于修改和添加实体(这将绑定到您的数据网格),另一个用于已删除的实体(应该在删除数据网格的记录时将其添加到其中)。但是,这将更新数据库中的每个字段,以便记录该记录;如果只想更新某些字段,则可以使DB上下文寿命更长。

如果您不想维护单独的列表,您可以通过从下面的接口派生POCOs来控制实体的状态。您可以在UI中控制此属性的状态,然后在存储库Save方法中读取该属性,而不是检查主键的默认值和维护单独的列表。

代码语言:javascript
复制
interface IEntity
{
    EntityState State { get; set;}
}

然后调用存储库上的Get方法,然后将记录列表绑定到数据网格。当您想要将更改持久化回DB时,您必须显式地调用存储库上的Save方法。

我希望这能帮到你。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24580862

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档