直接与上下文合作是不是一个好主意?例如,假设我有一个客户数据库,用户可以按名称搜索,显示列表,选择一个,然后编辑该客户的属性。
似乎我应该使用上下文来获取客户列表(映射到POCO或CustomerViewModels
),然后立即关闭上下文。然后,当用户选择列表中的CustomerViewModels
之一时,UI的客户属性部分就会填充。
接下来,他们可以更改名称,类型,网站地址,公司规模等。点击保存按钮后,我打开一个新的上下文,使用CustomerViewModel
中的ID来检索该客户记录,以及更新其每个属性。最后,我调用SaveChanges()
并关闭上下文。这是很多工作。
我的问题是为什么不直接使用上下文让它一直开放?我读过使用相同的上下文,生命周期很长,非常糟糕,不可避免地会引发问题。我的假设是,如果应用程序仅由一个人使用,我可以保持上下文打开并执行所有操作。但是,如果有很多用户,我想维护一个简洁的工作单元,从而根据每个请求打开和关闭上下文。
有什么建议吗?感谢。
@PGallagher - 感谢您的全面回答 @Brice - 您的输入也很有帮助
然而,@ Manos D.“冗余代码的缩影”评论让我有点担忧。让我举一个例子。假设我将客户存储在数据库中,而我的一个客户属性是CommunicationMethod。
[Flags]
public enum CommunicationMethod
{
None = 0,
Print = 1,
Email = 2,
Fax = 4
}
WPF中我的管理客户页面的UI将在客户通信方法(打印,电子邮件,传真)下包含三个复选框。我无法将每个复选框绑定到该枚举,这没有意义。此外,如果用户点击该客户,起床并去吃午餐......上下文会在那里停留数小时,这很糟糕。相反,这是我的思考过程。
最终用户从列表中选择一个客户。我新建了一个上下文,找到该客户并返回一个CustomerViewModel,然后关闭了上下文(为了简单起见,我已经将存储库保留了)。
using(MyContext ctx = new MyContext())
{
CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId));
}
现在,用户可以选中/取消选中打印,电子邮件,传真按钮,因为它们绑定到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();
}
}
}
你觉得这有什么问题吗?
答案 0 :(得分:17)
使用长时间运行的上下文是可以的;你只需要知道其含义。
上下文代表一个工作单元。无论何时调用SaveChanges,所跟踪实体的所有挂起更改都将保存到数据库中。因此,您需要将每个上下文的范围限定为有意义的内容。例如,如果您有一个用于管理客户的选项卡和另一个用于管理产品的选项卡,则可以为每个选项使用一个上下文,以便当用户单击客户选项卡上的保存时,他们对产品所做的所有更改也不会保存。 / p>
上下文跟踪了很多实体也可能会降低DetectChanges的速度。缓解此问题的一种方法是使用更改跟踪代理。
由于加载实体和保存该实体之间的时间可能相当长,因此点击乐观并发异常的可能性大于短期上下文。当在加载和保存实体之间进行外部更改时,会发生这些异常。 Handling these exceptions非常简单明了,但仍有一点需要注意。
使用WPF中的长期上下文可以做的一件很酷的事情是绑定到DbSet.Local属性(例如context.Customers.Local)。这是一个ObservableCollection,它包含所有未标记为删除的被跟踪实体。
希望这能为您提供更多信息,帮助您确定哪种方法可以提供帮助。
答案 1 :(得分:3)
Microsoft参考:
http://msdn.microsoft.com/en-gb/library/cc853327.aspx
他们说;
限制ObjectContext的范围
在大多数情况下,您应该创建 using语句中的ObjectContext实例(使用...结束使用in Visual Basic)。
这可以通过确保提高性能来提高性能 处理与对象上下文相关联的资源 代码退出语句块时自动执行。
然而,何时 控件绑定到由对象上下文管理的对象 只要绑定是,就应该维护ObjectContext实例 需要和手动处理。
有关更多信息,请参阅管理对象服务中的资源(实体框架)。 http://msdn.microsoft.com/en-gb/library/bb896325.aspx
其中说;
在长时间运行的对象上下文中,您必须确保上下文 在不再需要时处理。
StackOverflow参考:
这个StackOverflow问题也有一些有用的答案......
Entity Framework Best Practices In Business Logic?
有些人建议您将您的上下文提升到更高级别并从此处引用它,因此只保留一个上下文。
我的十便士价值:
在Using语句中包含Context,允许垃圾收集器清理资源,并防止内存泄漏。
显然在简单的应用程序中,这不是什么大问题,但是,如果你有多个屏幕,全部使用大量数据,你可能会遇到麻烦,除非你确定正确处理你的上下文。
因此,我采用了与您提到的方法类似的方法,我在每个存储库中添加了AddOrUpdate
方法,在其中传递了我的新实体或修改后的实体,并更新或添加了它取决于它是否存在。
更新实体属性:
关于更新属性,我使用了一个简单的函数,它使用反射将所有属性从一个实体复制到另一个实体;
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
我做的事情;
dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour)
当然,这减少了我需要为每个存储库编写的代码量!
答案 2 :(得分:0)
上下文未永久连接到数据库。它本质上是从磁盘加载的记录的内存缓存。它只会在您请求之前未加载的记录,强制刷新或将更改保存回磁盘时请求数据库中的记录。
打开上下文,抓取记录,关闭上下文,然后将修改后的属性从全新的上下文复制到对象是冗余代码的缩影。您应该单独保留原始上下文并使用它来执行SaveChanges()。
如果您正在寻求处理并发问题,您应该针对您的实体框架版本进行谷歌搜索“处理并发”。
我举例说明this。
编辑以回复评论:
因此,根据我的理解,您需要使用新值覆盖记录列的子集,而其余部分不受影响?如果是,是的,您需要在“新”对象上手动更新这几列。
我的印象是,您正在讨论反映客户对象所有字段的表单,并且旨在提供对整个客户记录的编辑访问权限。在这种情况下,没有必要使用新的上下文并且一个接一个地精心复制所有属性,因为最终结果(所有数据都覆盖了表单值而不管年龄)将是相同的。