实体框架和 WPF 最佳实践
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15012953/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Entity Framework and WPF best practices
提问by BBauer42
Is it ever a good idea to work directly with the context? For example, say I have a database of customers and a user can search them by name, display a list, choose one, then edit that customer's properties.
直接使用上下文是个好主意吗?例如,假设我有一个客户数据库,用户可以按名称搜索他们,显示一个列表,选择一个,然后编辑该客户的属性。
It seems I should use the context to get a list of customers (mapped to POCOs or CustomerViewModels) and then immediately close the context. Then, when the user selects one of the CustomerViewModelsin the list the customer properties section of the UI populates.
看来我应该使用上下文来获取客户列表(映射到 POCO 或CustomerViewModels),然后立即关闭上下文。然后,当用户选择CustomerViewModels列表中的其中一个时,UI 的客户属性部分将填充。
Next they can change the name, type, website address, company size, etc. Upon hitting a save button, I then open a new context, use the ID from the CustomerViewModelto retrieve that customer record, and update each of its properties. Finally, I call SaveChanges()and close the context. This is a LOT OF WORK.
接下来,他们可以更改名称、类型、网站地址、公司规模等。点击保存按钮后,我打开一个新上下文,使用 中的 IDCustomerViewModel检索该客户记录,并更新其每个属性。最后,我调用SaveChanges()并关闭上下文。这是很多工作。
My question is why not just work directly with the context leaving it open throughout? I have read using the same context with a long lifetime scope is very bad and will inevitably cause problems. My assumption is if the application will only be used by ONE person I can leave the context open and do everything. However, if there will be many users, I want to maintain a concise unit of work and thus open and close the context on a per request basis.
我的问题是为什么不直接处理上下文,让它始终保持打开状态?我读过使用具有长生命周期范围的相同上下文非常糟糕,并且不可避免地会导致问题。我的假设是,如果应用程序只能由一个人使用,我可以让上下文保持打开状态并做所有事情。但是,如果有很多用户,我想维护一个简洁的工作单元,从而在每个请求的基础上打开和关闭上下文。
Any suggestions? Thanks.
有什么建议?谢谢。
@PGallagher - Thanks for the thorough answer.
@Brice - your input is helpful as well
@PGallagher - 感谢您的详尽回答。
@Brice - 您的意见也很有帮助
However, @Manos D. the 'epitome of redundant code' comment concerns me a bit. Let me go through an example. Lets say I'm storing customers in a database and one of my customer properties is CommunicationMethod.
然而,@Manos D.“冗余代码的缩影”评论让我有点担心。让我通过一个例子。假设我将客户存储在数据库中,我的客户属性之一是 CommunicationMethod。
[Flags]
public enum CommunicationMethod
{
None = 0,
Print = 1,
Email = 2,
Fax = 4
}
The UI for my manage customers page in WPF will contain three check boxes under the customer communication method (Print, Email, Fax). I can't bind each checkbox to that enum, it doesn't make sense. Also, what if the user clicked that customer, gets up and goes to lunch... the context sits there for hours which is bad. Instead, this is my thought process.
WPF 中管理客户页面的 UI 将包含客户通信方法(打印、电子邮件、传真)下的三个复选框。我无法将每个复选框绑定到该枚举,这没有意义。另外,如果用户点击那个客户,起床去吃午饭怎么办……上下文在那里坐了几个小时,这很糟糕。相反,这是我的思考过程。
End user chooses a customer from the list. I new up a context, find that customer and return a CustomerViewModel, then the context is closed (I've left repositories out for simplicity here).
最终用户从列表中选择一个客户。我新建了一个上下文,找到那个客户并返回一个 CustomerViewModel,然后关闭上下文(为了简单起见,我在这里省略了存储库)。
using(MyContext ctx = new MyContext())
{
CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId));
}
Now the user can check/uncheck the Print, Email, Fax buttons as they are bound to three bool properties in the CustomerViewModel, which also has a Save() method. Here goes.
现在用户可以选中/取消选中 Print、Email、Fax 按钮,因为它们绑定到 CustomerViewModel 中的三个 bool 属性,该属性也有一个 Save() 方法。开始。
public class CustomerViewModel : ViewModelBase
{
Customer _customer;
public CustomerViewModel(Customer customer)
{
_customer = customer;
}
public bool CommunicateViaEmail
{
get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email); }
set
{
if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email)) return;
if (value)
_customer.CommunicationMethod |= CommunicationMethod.Email;
else
_customer.CommunicationMethod &= ~CommunicationMethod.Email;
}
}
public bool CommunicateViaFax
{
get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); }
set
{
if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax)) return;
if (value)
_customer.CommunicationMethod |= CommunicationMethod.Fax;
else
_customer.CommunicationMethod &= ~CommunicationMethod.Fax;
}
}
public bool CommunicateViaPrint
{
get { return _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print); }
set
{
if (value == _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print)) return;
if (value)
_customer.CommunicateViaPrint |= CommunicationMethod.Print;
else
_customer.CommunicateViaPrint &= ~CommunicationMethod.Print;
}
}
public void Save()
{
using (MyContext ctx = new MyContext())
{
var toUpdate = ctx.Customers.Find(_customer.Id);
toUpdate.CommunicateViaEmail = _customer.CommunicateViaEmail;
toUpdate.CommunicateViaFax = _customer.CommunicateViaFax;
toUpdate.CommunicateViaPrint = _customer.CommunicateViaPrint;
ctx.SaveChanges();
}
}
}
Do you see anything wrong with this?
你觉得这有什么不对吗?
回答by bricelam
It is OK to use a long-running context; you just need to be aware of the implications.
使用长时间运行的上下文是可以的;您只需要了解其中的含义。
A context represents a unit of work. Whenever you call SaveChanges, all the pending changes to the entities being tracked will be saved to the database. Because of this, you'll need to scope each context to what makes sense. For example, if you have a tab to manage customers and another to manage products, you might use one context for each so that when a users clicks save on the customer tab, all of the changes they made to products are not also saved.
上下文代表一个工作单元。每当您调用 SaveChanges 时,对被跟踪实体的所有未决更改都将保存到数据库中。因此,您需要将每个上下文的范围限定在有意义的范围内。例如,如果您有一个用于管理客户的选项卡和另一个用于管理产品的选项卡,您可以为每个选项卡使用一个上下文,这样当用户单击客户选项卡上的保存时,他们对产品所做的所有更改都不会同时保存。
Having a lot of entities tracked by a context could also slow down DetectChanges. One way to mitigate this is by using change tracking proxies.
通过上下文跟踪大量实体也会减慢 DetectChanges 的速度。缓解这种情况的一种方法是使用更改跟踪代理。
Since the time between loading an entity and saving that entity could be quite long, the chance of hitting an optimistic concurrency exception is greater than with short-lived contexts. These exceptions occur when an entity is changed externally between loading and saving it. Handling these exceptionsis pretty straightforward, but it's still something to be aware of.
由于加载实体和保存实体之间的时间可能很长,因此遇到乐观并发异常的机会比短期上下文要大。当实体在加载和保存之间发生外部更改时,会发生这些异常。处理这些异常非常简单,但仍然需要注意。
One cool thing you can do with long-lived contexts in WPF is bind to the DbSet.Local property (e.g. context.Customers.Local). this is an ObservableCollection that contains all of the tracked entities that are not marked for deletion.
您可以使用 WPF 中的长期上下文做的一件很酷的事情是绑定到 DbSet.Local 属性(例如 context.Customers.Local)。这是一个 ObservableCollection,其中包含所有未标记为删除的跟踪实体。
Hopefully this gives you a bit more information to help you decide which approach to help.
希望这可以为您提供更多信息,以帮助您决定使用哪种方法提供帮助。
回答by PGallagher
Microsoft Reference:
微软参考:
http://msdn.microsoft.com/en-gb/library/cc853327.aspx
http://msdn.microsoft.com/en-gb/library/cc853327.aspx
They say;
他们说;
Limit the scope of the ObjectContext
In most cases, you should create an ObjectContext instance within a using statement (Using…End Using in Visual Basic).
This can increase performance by ensuring that the resources associated with the object context are disposed automatically when the code exits the statement block.
However, when controls are bound to objects managed by the object context, the ObjectContext instance should be maintained as long as the binding is needed and disposed of manually.
For more information, see Managing Resources in Object Services (Entity Framework). http://msdn.microsoft.com/en-gb/library/bb896325.aspx
限制 ObjectContext 的范围
在大多数情况下,您应该在 using 语句(在 Visual Basic 中使用...结束使用)中创建一个 ObjectContext 实例。
这可以通过确保在代码退出语句块时自动处理与对象上下文关联的资源来提高性能。
但是,当控件绑定到对象上下文管理的对象时,只要需要绑定并手动处理,就应该维护 ObjectContext 实例。
有关更多信息,请参阅管理对象服务中的资源(实体框架)。http://msdn.microsoft.com/en-gb/library/bb896325.aspx
Which says;
其中说;
In a long-running object context, you must ensure that the context is disposed when it is no longer required.
在长时间运行的对象上下文中,您必须确保在不再需要时释放上下文。
StackOverflow Reference:
StackOverflow 参考:
This StackOverflow question also has some useful answers...
这个 StackOverflow 问题也有一些有用的答案......
Entity Framework Best Practices In Business Logic?
Where a few have suggested that you promote your context to a higher level and reference it from here, thus keeping only one single Context.
一些人建议您将上下文提升到更高级别并从此处引用它,从而仅保留一个上下文。
My ten pence worth:
我的十便士价值:
Wrapping the Context in a Using Statement, allows the Garbage Collector to clean up the resources, and prevents memory leaks.
将上下文包装在 Using 语句中,允许垃圾收集器清理资源,并防止内存泄漏。
Obviously in simple apps, this isn't much of a problem, however, if you have multiple screens, all using alot of data, you could end up in trouble, unless you are certain to Dispose your Context correctly.
显然,在简单的应用程序中,这不是什么大问题,但是,如果您有多个屏幕,并且都使用大量数据,则最终可能会遇到麻烦,除非您确定正确处理您的上下文。
Hence I have employed a similar method to the one you have mentioned, where I've added an AddOrUpdateMethod to each of my Repositories, where I pass in my New or Modified Entity, and Update or Add it depending upon whether it exists.
因此,我采用了与您提到的方法类似的方法,其中我AddOrUpdate向每个存储库添加了一个方法,在其中传入我的新实体或修改后的实体,并根据它是否存在更新或添加它。
Updating Entity Properties:
更新实体属性:
Regarding updating properties however, I've used a simple function which uses reflection to copy all the properties from one Entity to Another;
然而,关于更新属性,我使用了一个简单的函数,它使用反射将所有属性从一个实体复制到另一个实体;
Public Shared Function CopyProperties(Of sourceType As {Class, New}, targetType As {Class, New})(ByVal source As sourceType, ByVal target As targetType) As targetType
Dim sourceProperties() As PropertyInfo = source.GetType().GetProperties()
Dim targetProperties() As PropertyInfo = GetType(targetType).GetProperties()
For Each sourceProp As PropertyInfo In sourceProperties
For Each targetProp As PropertyInfo In targetProperties
If sourceProp.Name <> targetProp.Name Then Continue For
' Only try to set property when able to read the source and write the target
'
' *** Note: We are checking for Entity Types by Checking for the PropertyType to Start with either a Collection or a Member of the Context Namespace!
'
If sourceProp.CanRead And _
targetProp.CanWrite Then
' We want to leave System types alone
If sourceProp.PropertyType.FullName.StartsWith("System.Collections") Or (sourceProp.PropertyType.IsClass And _
sourceProp.PropertyType.FullName.StartsWith("System.Collections")) Or sourceProp.PropertyType.FullName.StartsWith("MyContextNameSpace.") Then
'
' Do Not Store
'
Else
Try
targetProp.SetValue(target, sourceProp.GetValue(source, Nothing), Nothing)
Catch ex As Exception
End Try
End If
End If
Exit For
Next
Next
Return target
End Function
Where I do something like;
我在哪里做类似的事情;
dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour)
This reduces the amount of code I need to write for each Repository of course!
这当然减少了我需要为每个存储库编写的代码量!
回答by Manos Dilaverakis
The context is not permanently connected to the database. It is essentially an in-memory cache of records you have loaded from disk. It will only request records from the database when you request a record it has not previously loaded, if you force it to refresh or when you're saving your changes back to disk.
上下文不会永久连接到数据库。它本质上是您从磁盘加载的记录的内存缓存。它只会在您请求以前未加载的记录、强制刷新或将更改保存回磁盘时从数据库请求记录。
Opening a context, grabbing a record, closing the context and then copying modified properties to an object from a brand new context is the epitomy of redundant code. You are supposed to leave the original context alone and use that to do SaveChanges().
打开上下文、抓取记录、关闭上下文,然后将修改后的属性从全新的上下文复制到对象是冗余代码的缩影。您应该单独保留原始上下文并使用它来执行 SaveChanges()。
If you're looking to deal with concurrency issues you should do a google search about "handling concurrency" for your version of entity framework.
如果您想处理并发问题,您应该为您的实体框架版本搜索有关“处理并发”的谷歌搜索。
As an example I have found this.
作为一个例子,我发现了这个。
Edit in response to comment:
编辑以回应评论:
So from what I understand you need a subset of the columns of a record to be overridden with new values while the rest is unaffected? If so, yes, you'll need to manually update these few columns on a "new" object.
因此,据我所知,您需要使用新值覆盖记录列的子集,而其余列不受影响?如果是这样,是的,您需要手动更新“新”对象上的这几列。
I was under the impression that you were talking about a form that reflects all the fields of the customer object and is meant to provide edit access to the entire customer record. In this case there's no point to using a new context and painstakingly copying all properties one by one, because the end result (all data overridden with form values regardless of age) will be the same.
我的印象是,您在谈论一种反映客户对象所有字段的表单,旨在提供对整个客户记录的编辑访问权限。在这种情况下,没有必要使用新的上下文并煞费苦心地一个一个地复制所有属性,因为最终结果(所有数据都被表单值覆盖,而不管年龄大小)将是相同的。

