实体框架 4.1 Code First 与 N 层节省双倍
我有一个 WCF 项目,其中有一个有很多孩子的实体。我有一个业务层和一个数据访问层,在数据访问层中,我有可以检索并保存到数据库的存储库。我对 EF 的理解是,您可以在需要时创建和销毁 DataContext。举个例子,假设我有一个 Person 实体和一个 Book 实体(这不是我的应用程序,只是为了尝试说明问题)。
假设 Person 如下所示。
Person
string Name
vitual ICollection<Book> Books
对于 Book 可能是这样的
Book
string Title
Person PersonLending
现在在我的 BLL 中,我想读取 person 表,然后将一本书分配给该人,但该人已经存在于数据库中,因此 BLL 调用人员实体的存储库。
var person = repository.GetPerson("John Doe");
我的存储库有这段代码。
using(var context = new MyContext())
{
return (from p in context.Person
where p.Name == person
select p).FirstOrDefault());
}
现在,在 BLL 中,我创建了一本新书,并将此人分配给它。
var book = new Book();
book.PersonLending = person;
book.Title = "New Book";
repository.SaveBook();
最后,我尝试在存储库中保存这本书。
using(var context = new MyContext())
{
context.Book.Add(book);
context.SaveChanges();
}
现在发生的事情是我在表中得到了两个 Person 行。我的理解是,这是由于第一个上下文被破坏,而第二个上下文不知道 Person 已经存在而引起的。
我想我有两个问题。
- 在 WCF 中处理 DataContext 的最佳实践是什么?是否应该只有一个数据上下文从一个类传递到另一个类并向下传递到存储库中。
- 或者有什么办法可以保存这个。
我尝试将 Person 的 EntityState 设置为 Unchanged,但似乎不起作用。
编辑:
我已经更改了一些内容,以便为每个请求(AfterReceiveRequest 和 BeforeSendReply)创建一个新的 DataContext。
public class EFWcfDataContextAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase){}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters){}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (var endpoint in channelDispatcher.Endpoints)
{
endpoint.DispatchRuntime.MessageInspectors.Add(new EFWcfDataContextInitializer());
//endpoint.DispatchRuntime.InstanceContextInitializers.Add(new EFWcfDataContextInitializer());
}
}
}
初始化器
public class EFWcfDataContextInitializer : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
instanceContext.Extensions.Add(new EFWcfDataContextExtension(new MyDataContext()));
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
WcfDataContextFactory.Dispose();
}
}
和扩展
public class EFWcfDataContextExtension : IExtension<InstanceContext>
{
public ICoreDataContext DataContext { get; private set; }
public EFWcfDataContextExtension(ICoreDataContext coreDataContext)
{
if(DataContext != null)
throw new Exception("context is not null");
DataContext = coreDataContext;
}
public void Attach(InstanceContext owner){}
public void Detach(InstanceContext owner) {}
}
这似乎给出了一个全新的问题。我通过调用 OperationContext.Current.InstanceContext.Extensions.Find().DataContext 获取当前上下文,但现在看来这两个上下文相互影响。对于同一请求,第一个请求将返回空记录,第二个请求将成功。它们都位于唯一的会话中,并且当它们都被创建时,它们都是 null 并被创建为新的 DataContext。当我第一次检查 Database.Connection 属性时,它已关闭,并且手动尝试打开它会产生更多错误。我真的认为这可以解决问题。
我也尝试使用 IContractBehaviour 执行此操作,得到相同的结果。所以要么我做错了什么,要么我错过了一些明显的事情。
PS:在发表原始帖子之前,我尝试将状态设置为“未更改”。 PPS:如果有人想知道,我的数据工厂只有这两种方法
public static void Dispose()
{
ICoreDataContext coreDataContext = OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
coreDataContext.Dispose();
coreDataContext = null;
}
public static ICoreDataContext GetCurrentContext()
{
var context = OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
if (context != null)
{
if (context.Database.Connection.State == ConnectionState.Closed)
context.Database.Connection.Open();
}
return context;
}
I have a WCF project with a entities with lots of children. I have a business layer and a data access layer, in the data access layer I have repositories that retrieve and save to my database. My understanding of EF is that you create and destroy the DataContext as and when you need it. As an example, lets say I have the a Person entity, and a Book entity (this is not my application, but just to try and illustrate the problem).
Assume Person looks as follows.
Person
string Name
vitual ICollection<Book> Books
With Book maybe something like this
Book
string Title
Person PersonLending
Now in my BLL I want to read the person table and then assign a book to that person, but the person already exists in the database, so the BLL calls to the repository for a person entity.
var person = repository.GetPerson("John Doe");
My repository has this code.
using(var context = new MyContext())
{
return (from p in context.Person
where p.Name == person
select p).FirstOrDefault());
}
Now in the BLL I create a new book and assign this person to it.
var book = new Book();
book.PersonLending = person;
book.Title = "New Book";
repository.SaveBook();
Finally in the repository I try to save back the book.
using(var context = new MyContext())
{
context.Book.Add(book);
context.SaveChanges();
}
Now what happens is I get two Person rows in the table. My understanding is that this is caused by the first context being destroyed, and the second context not knowing that Person already exists.
I have two questions I guess.
- What is the best practice for handling DataContext in WCF ? Should there just be a single datacontext being passed from class to class and down into the repositories.
- Or is there a way to make this save.
I have tried setting the EntityState to Unchanged on Person, but it doesn't seem to work.
Edit:
I have changed things to create a new DataContext per request (AfterReceiveRequest and BeforeSendReply).
public class EFWcfDataContextAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase){}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters){}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (var endpoint in channelDispatcher.Endpoints)
{
endpoint.DispatchRuntime.MessageInspectors.Add(new EFWcfDataContextInitializer());
//endpoint.DispatchRuntime.InstanceContextInitializers.Add(new EFWcfDataContextInitializer());
}
}
}
Initializer
public class EFWcfDataContextInitializer : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
instanceContext.Extensions.Add(new EFWcfDataContextExtension(new MyDataContext()));
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
WcfDataContextFactory.Dispose();
}
}
And the extension
public class EFWcfDataContextExtension : IExtension<InstanceContext>
{
public ICoreDataContext DataContext { get; private set; }
public EFWcfDataContextExtension(ICoreDataContext coreDataContext)
{
if(DataContext != null)
throw new Exception("context is not null");
DataContext = coreDataContext;
}
public void Attach(InstanceContext owner){}
public void Detach(InstanceContext owner) {}
}
This seems to give a brand new problem. I get the current context by invoking OperationContext.Current.InstanceContext.Extensions.Find().DataContext, but it now seems that the two context influence each other. On the same request the first one will return a null record and the second one will succeed. They are both in unique sessions, and when they are both created they are null and created as new DataContext. When I check the Database.Connection property on the first it is closed, and manually trying to open it creates more errors. I really thought this would solve the issue.
I have also tried doing this with a IContractBehaviour, with the same result. So either I am doing something wrong or I am missing something obvious.
PS: I tried setting the state to Unchanged before I made the original post.
PPS: In case anyone wonders, my datafactory simply has these two methods
public static void Dispose()
{
ICoreDataContext coreDataContext = OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
coreDataContext.Dispose();
coreDataContext = null;
}
public static ICoreDataContext GetCurrentContext()
{
var context = OperationContext.Current.InstanceContext.Extensions.Find<EFWcfDataContextExtension>().DataContext;
if (context != null)
{
if (context.Database.Connection.State == ConnectionState.Closed)
context.Database.Connection.Open();
}
return context;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
你的理解是绝对正确的。将数据传递回服务后,新上下文将不知道
Book
或Person
。在Book
上调用 Add 可以将对象图中的每个未知实体标记为Added
。这是分离场景的一个非常大的问题。解决方案不是共享上下文这是处理问题的最糟糕的方法,因为它引入了还有很多其他问题,最后还是不行。每个服务调用使用新的上下文。
试试这个:
或者这个:
这个问题是更多复杂,一旦您开始发送关系发生变化的更复杂的对象图。您最终将首先加载对象图并将更改合并到附加实体中。
Your understanding is absolutely correct. Once you pass the data back to the service the new context doesn't know neither the
Book
orPerson
. Calling Add on theBook
has effect of marking every unknown entity in the object graph asAdded
. This is very big problem of detached scenarios.The solution is not sharing the context That is the worst way to deal with the problem because it introduces a lot of other problems and at the end it will still not work. Use a new context per each service call.
Try this:
or this:
This problem is more complex and once you start to send more complicated object graphs with changes in relations. You will end up in loading object graph first and merging changes into attached entities.