我第一次在复杂的网格数据库环境中使用了实体框架,我需要一些策略来完成这个任务。我获得了由“each反向代码优先”(映射相同)自动生成的POCO类,对于每个类,还有一个存储库,用于查询或执行简单的CRUD操作:
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查询,该查询可以对存储库的结果集进行修饰,并将其绑定到网格,如下所示:
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结果,因为:
由于这些问题,我希望以这样的方式直接绑定POCO OrderClass:
this.OrderRepository.AsQueryable().Select()并将“间接”字段"DataPropertyName“设置为:
列"Name“-> Customer.Name “城市”栏目-> Customer.City
但同样,它不适合,因为:
因此,在故事的结尾,当EF涉及到数据绑定时,这是一个很难管理的任务(在我们的软件中有上千个这样的例子)。
我想知道是否有一个好的策略来完成这项任务。
我讨论了如何通过使用ViewModel (也实现了INotifyPropertyChanged)对象来绑定到gridView来处理这个问题:
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中。
抱歉,如果我长篇大论的话,但我尽力做到最清楚。提前谢谢。
发布于 2014-07-21 17:21:08
为了解决这个问题,我在稍微复杂的结构中使用了@User978139提出的ViewModel解决方案。特别是,我创建了包装订单模型的OrderViewModel,该模型的CustomerId和EmployeeId属性具有可读性和可写性。请注意,在其设置程序中,我添加了一些代码,以强制客户和雇员属性更改,并引发相应的PropertyChanged()事件。这是代码:
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的代码:
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对象获取包装模型的方法。这就是一个例子:
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);这不是一个相当简单的解决方案,但正如我所说的,它工作得很好,所以我将这个线程标记为已回答。希望有人能找到更好的解决办法。
发布于 2014-07-05 08:51:29
您可以使用ViewModel方法将表示逻辑与视图粘合在一起。您不会使用它来表示绑定到数据网格的示例中的单个记录,而且由于支持双向绑定,通常在WPF应用程序中使用它。
使用扁平化的方法来表示多个对象可能不是最好的主意,因为客户可以有多个订单,因此,当您按照问题中的建议修改客户的详细信息时,您将需要强制使用同一个客户的PropertyChanged到其他记录。您可能想要实现的是应用程序中的主细节网格视图,您可以在其中展开客户行来查看他们的订单。
如果您想继续使用扁平结构的方法,您应该创建一个包装类来包含从存储库加载的实体。然后,您将公开包装类上的属性,引用来自基础实体的值属性。示例:
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的存储库。
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方法中读取该属性,而不是检查主键的默认值和维护单独的列表。
interface IEntity
{
EntityState State { get; set;}
}然后调用存储库上的Get方法,然后将记录列表绑定到数据网格。当您想要将更改持久化回DB时,您必须显式地调用存储库上的Save方法。
我希望这能帮到你。
https://stackoverflow.com/questions/24580862
复制相似问题