问题
We have a multi-layered Asp.NET Web Forms application. The data layer has a class called DataAccess
which impements IDisposable
and has an instance of our Entity Framework Object Context as a private field. The class has a number of public methods returning various collections of Entities and will dispose its Object Context when it is disposed.
Due to a number of problems we've been facing, we decided it would be a big plus to keep the Object Context (or an instance of DataAccess
) in scope for longer on the server. A suggestion was made to keep an instance in the HttpContext.Current.Items
collection from this post in order to have one instance per Http request.
What I'm wondering is: What issues / concerns / problems would arise from storing an instance of our Object Context in the HttpContext.Current.Session
object????
- I'm assuming that the Session object is finalised and set for garbage collection when a user's session expires, so the instance will be disposed properly.
- I'm assuming most default browser settings will let our app place its SessionId cookie without qualms.
- The amount of data the Object Context will be dealing with is not enormous and will not pose a problem for our decent server hardware, with regards to caching over time and relatively few concurrent users.
This will be relatively quick to implement and will not affect our many existing unit tests.
We'll be using AutoFac and a ServiceProvider class to supply instances. When an instance of the ObjectContext is required it will be returned by code similar to this:
private static Entities GetEntities(IContext context)
{
if (HttpContext.Current == null)
{
return new Entities();
}
if (HttpContext.Current.Session[entitiesKeyString] == null)
{
HttpContext.Current.Session[entitiesKeyString] = new Entities();
}
return (Entities)HttpContext.Current.Session[entitiesKeyString];
}
Cheers.
回答1:
Storing an ObjectContext
in the session state is not something I would consider to be a good practice since the class is intended to encapsulate a unit-of-work pattern - you load up some data (entities), modify them, commit your changes (which are tracked by the UOW), and then you're done with it. UOW objects are not intended or designed to be long-lived.
That said, it can be done without causing any major catastrophes, you just have to make sure you understand what's going on behind the scenes. Please read on if you plan on doing this so that you know what you're getting yourself into and are aware of the trade-offs.
I'm assuming that the Session object is finalised and set for garbage collection when a user's session expires, so the instance will be disposed properly.
This is actually inaccurate, or at least seems to be based on the way it's worded. Session expiry/logout will not immediately cause any of the items to be disposed. They will eventually be finalized/disposed but that is up to the garbage collector and you have no control over when it happens. The biggest potential problem here is if you happen to manually open a connection on the ObjectContext
, which won't get closed automatically - if you're not careful, you could end up leaking database connections, something that wouldn't be uncovered with regular unit tests/integration tests/live tests.
The amount of data the Object Context will be dealing with is not enormous and will not pose a problem for our decent server hardware, with regards to caching over time and relatively few concurrent users.
Just keep in mind that the growth is unbounded. If a particular user decides to use your site for 12 straight hours running different queries all day then the context will just keep getting bigger and bigger. An ObjectContext
doesn't have its own internal "garbage collection", it doesn't scavenge cached/tracked entities that haven't been used for a long time. If you're sure that this isn't going to be a problem based on your use cases then fine, but the main thing that should be bothering you is the fact that you lack control over the situation.
Another issue is thread-safety. ObjectContext
is not thread-safe. Session access is normally serialized, so that one request will block waiting for its session state until another request for the same session is complete. However, if somebody decides to make optimizations later on, specifically the optimization of page-level read-only sessions, requests will no longer hold an exclusive lock and it would be possible for you to end up with various race conditions or re-entrancy problems.
Last but not least is of course the issue of multi-user concurrency. An ObjectContext
caches its entities forever and ever until it is disposed. If another user changes the same entities on his own ObjectContext
, the owner of the first ObjectContext
will never find out about that change. These stale data problems can be infuriatingly difficult to debug, because you can actually watch the query go to the database and come back with fresh data, but the ObjectContext
will overwrite it with the old, stale data that's already in the cache. This, in my opinion, is probably the most significant reason to avoid long-lived ObjectContext
instances; even when you think you've coded it to grab the most recent data from the database, the ObjectContext
will decide that it's smarter than you and hand you back the old entities instead.
If you're aware of all of these issues and have taken steps to mitigate them, fine. But my question would be, why exactly do you think that a session-level ObjectContext
is such a great idea? Creating an ObjectContext
is really a very cheap operation because the metadata is cached for the entire AppDomain. I'd wager a guess that either you're under the mistaken impression that it's expensive, or you're trying to implementing complicated stateful processes over several different web pages, and the long-term consequences of the latter are far worse than any specific harm you may do by simply putting an ObjectContext
into the session.
If you're going to go ahead and do it anyway, just make sure you're doing it for the right reasons, because there aren't a whole lot of good reasons to do this. But, as I said, it's definitely possible to do, and your app is not going to blow up as a result.
Update - for anyone else considering downvoting this because "multiple requests on the same session could cause thread-safety issues", please read the bottom of the ASP.NET Session State Overview documentation. It is not just individual accesses of the session state that are serialized; any request that acquires a session keeps an exclusive lock on the session that is not released until the entire request is complete. Excepting some of the optimizations I listed above, it is impossible in the default configuration for there to ever be two simultaneous requests holding references to the same session-local instance of an ObjectContext
.
I still wouldn't store an ObjectContext
in the session state for several of the reasons listed above, but it is not a thread-safety issue unless you make it one.
回答2:
You should use one ObjectContext per request, you shouldn't store it is Session. It is easy to ruin data in ObjectContext stored for a long time:
What if you insert data that don't violate rules in ObjectContext, but violate rules in database? If you insert a row that violates the rules, will you be deleting it from context? Image situation: You use one context and suddenly you have request that changes data in one table, adds row to another table, then you call SaveChanges(). One of changes throws constraint violation error. How do you clean it up? Cleaning context is not easy, it is easier just to get new one in next request.
What if someone deletes data from database, while it is still in context? ObjectContext caches data and doesn't look from time to time to check if it is still there or if they changed:)
What if someone changes web.config and Session is lost? It seems as if you want to rely on Session to store information about logged in user. Forms authentication cookie is more reliable place to store this information. Session can be lost in many situations.
ObjectContext was designed to be short lived, it is best to create it in request when needed and dispose at the end of it.
If context per request doesn't work for you, you are propably doing something wrong, but don't make it worse by using Session.
来源:https://stackoverflow.com/questions/2383966/entity-framework-object-context-in-asp-net-session-object