Entity Framework context per request
原文发布时间为:2011-09-24 —— 来源于本人的百度文章 [由搬家工具导入]
http://www.blog.cyberkinetx.com/2011/05/15/entity-framework-context-per-request/
Are you still using the
?1234using(DBContext context = newDBContext()){ ..}
way of accessing the database? I mean that’s cool, at least you are sure you don’t have connection pooling penalties. But where do you write this code? Do you have a Data Access Layer? How about accessing the Navigation Properties outside the using block? How about assuring a developer does not forget about the context restrictions and tries to access a related table outside the context?
I’ve seen developers creating the context inline without a using block, and then not bothering about disposing it, leaving this “monkey business” to the garbage collector. Because it was a web service project with a lot of access I volunteered to fix it, and encapsulate all contexts in using blocks. I didn’t bother to test it.. I mean what could go wrong, right? It was the DAL. Because of a combination of unfortunate events, the code found it’s way to live and crashed.. Somebody was using a Navigation Property in the Business Layer. The only reason why it did not crash before was the garbage collector, it was not disposing the context fast enough, so you could still write some code in the business before the context was disposed. A race against time actually..
On the next project, my first idea was to make all Navigation Properties in the entities as internal only to the DAL. But then cam the architect suggesting we use linq on the entities from the BL. That means the Entity Framework context must continue hes existence at the Business Layer (but not accessible). The solution? Unit Of Work.
First of all we created the Data Access Layer. Entity Framework as a concept is a DAL by itself, but we needed to separate it from our code for the sake of UT simplicity and because it simply is a good practice. It’s a lot simple to mock an interface than the EF. Inside the DAL project we created the Repository class that did all the DB access job. Actually it has only CRUD operations. For example:
?0102030405060708091011publicIQueryable Customers { get{ ... } } publicvoidAdd(Customer customer){ ..} publicvoidDelete(Object entity){ ..}
You may argue this are not CRUD because there is not Update operation. But because the Customer is exposed by the Customers property it can be updated by anybody outside the repository, and so it does expose the Update operation.
I skipped the implementation details as you are not ready to see it. All at it’s time. Let’s see what this CRUD operations give us. We have a repository that doesn’t have for ex. GetCustomerOwingMoney(), or GetNewOrders(). From a OOP point of view that would be operations on the Customer collection and Order collections. It does make sens not to write them in our BL. Very well then, let’s write them on the IQueryable<Customer> collection:
?1234publicIQueryable<Customer> OwingMoney(thisIQueryable<Customer> customers){ returncustomers.Where(x => x.Balance < 0);}
Hey, that’s an extension method! We followed the OOP way and got ourselves a collection of customers with a OwingMoney() method on it. Ain’t that awesome? As part of our project we created extension methods for all entities that needed operations on the collections (before putting just any method as an extension think first if it’s a BL related or it’a an operation related to that particular type). As a convention the classes with extension methods are called CustomerExtensions, OrderExtensions, etc. Pushing that forward, we also have partial classes for the entities with properties and methods, like OwsMoney (taking the previous example), or Suspend() to suspend a customer if he refuses to pay. So the extensions can be used from the BL in a kind of fluent way.
A question that might arise at this point is – if we have all this logic in the extension method and in the partial classes, what do we actually put in the BL? And indeed there is no thin border where the extension methods end and the BL starts. So far our rule is to put all the reusable code where possible to the extension methods and partial classes, the rest would be the BL. For example, let’s say we need a method to suspend all customers that owe money to the shop, and have more than 2 open orders (i guess it has no logic but just for the sample sake), that can be done as an extension on the IQueryable<Customer>, but it will not be reused anywhere as it’s an action that will be triggered by some scheduled process. So it makes a lot of sense to write it in the BL:
?123456publicvoidSuspendBadCustomers(){ repository.Customers.OwingMoney() .Where(x => x .Orders.OpenOrders.Count() > 2).ToList() .ForAll(x => x.Suspend());}
Does that make sense? Not with a classic repository. As far we didn’t save the changes. We can just leave it like that. It will be saved.. eventually. And how in the world we dispose the context?? Oh well, we have the UnitOfWork for that.
Before I start discussing the UnitOfWork implementation I will assume you are familiar with Dependency Injection and Inversion of Control as explaining them is out of the scope of this post.
For the current post I will do a very simple UnitOfWork just to satisfy the problem of having a context persisted along the lifetime of the HTTP Request. More complext implementation would mean supporting multiple contexts, supporting different object persistence on the UnitOfWork.
?01020304050607080910111213141516171819202122232425publicclassUnitOfWork : IUnitOfwork, IDisposable{ publicObjectContext Context { get; set; } publicDbTransaction Transaction { get; set; } publicvoidCommit() { if(Context != null) { ObjectContext.SaveChanges(); Transaction.Commit(); } } publicvoidDispose() { if(Context != null) { Transaction.Dispose(); Transaction = null; Context.Dispose(); Context = null; } }}
Here we have 2 properties for Context and another for Transaction. Because of the per request behavior, we can’t use the TransactionScope any more, so we’ll go with a bit old fashion way of working with transactions.
Next step would be to configure the IoC container to treat IUnitOfWork with a lifetime that would give the same instance for a HTTP Request. Meaning, whenever I’ll call my IoC like
?1IUnitOfWork unit = IoCContainer.Resolve<IUnitOfWork>();
I will be getting the same instance of the UnitOfWork in a single HTTP Request (to not be confused with HTTP Session).
Next step is to configure the Global.asax to handle the UnitOfWork, committing it when the request ends, and just disposing it when an exception is thrown so the transaction will be rolled back. What you need to add to the Global.asax:
?01020304050607080910111213publicvoidApplication_EndRequest(Object sender, EventArgs e){ IUnitOfWork unit = IoCContainer.Resolve<IUnitOfWork>(); unit.Commit(); unit.Dispose();} publicvoid Application_Error(Object sender, EventArgs e){ IUnitOfWork unit = IoCContainer.Resolve<IUnitOfWork>(); unit.Dispose(); //don't forget to treat the error here}
No actions are required on BeginRequest event. But so far the Entity Framework context isn’t initialized anywhere. It would make sense to initialize the context only when required. Some request might not hit the DB so why the extra penalty? Because I don’t want my BL to know much about EF I decided to do the initialization in Repository. I created a GetContext() method that returns the context whenever it is required. And because dependency injection is used the UnitOfWork can be set up as a parameter in the constructor and it will be injected when the Repository is instantiated (preferably by IoC as well):
?0102030405060708091011121314151617181920212223publicclassRepository : IRepository{ privateIUnitOfWork unitOfWork; publicRepository(IUnitOfWork unitOfWorkk) { this.unitOfWork = unitOfWork; } //CRUD operations code would be here //.. privateOurDbContext GetContext() { if(unitOfWork.Context == null) { unitOfWork.Context = newOurDbContext(); unitOfWork.Transaction = context.Connection.BeginTransaction(); } return(OurDbContext)unitOfWork.Context; }}
We are almost there. Just have to update our CRUD operations example, with the GetContext():
?0102030405060708091011publicIQueryable<Customer> Customers { get{ returnGetContext().Customers; } } publicvoidAdd(Customer customer){ GetContext().Customers.Add(customer);} publicvoidDelete(Object entity){ GetConetxt().DeleteObject(entity);}
We are there. Let’s summarize what we got. We have a UnitOfWork that is a-kind-of-singleton, that is retrieved using IoC, and will have the same instance as long it’s in the context of the same HTTP Request. In the repository whenever the first db operations is called a context and a transaction is created and saved on the UnitOfWork. The context will be reused in the repository as long as it’s doing operations for the same HTTP Request. Whenever the HTTP Request ends (a HTTP Reply is issued to the client), in case of no exceptions the transaction will be committed and all changes will be saved to the database, in case of exceptions the transaction will be reverted and a nice message must be issued to the end user, and a error log should be created. On the next request another UnitOfWork is created and another context.
For the Business Layer, we have safe access to the Navigation Properties, extension methods can be used for any entities. Performance increases because BL does not access repository methods for different actions creating new instances of the ObjectContext.
I’ve been asked if keeping a context for so long isn’t a performance issue by itself. The answer would be “depends”. If you are building a web site like Facebook you’ll probably search for other options (and most probably give up on EF). The pool size for the context has a default size of 100. That means you can have 100 requests that are processed in parallel, the 101 will wait for a context to be free. When you get to more then 100 parallel requests you’ll probably have other bottlenecks to worry about, and will already be thinking about load balancing.
If I may return to the problem of SaveChanges(). In the presented examples it is never called from Repository or BL as the examples are fairly simple. In more complicated scenarios it will be necessary so in our implementation we have a Save() method implemented in the repository that simply calls the SaveChanges() on the context. A scenario when you need it would be when you add to repository new entities, and then do a Where() and expect to find them. Well.. if you want’ save them before the Where() operation, you won’t.. That’s how EF works. Same with the changes. So if you need to add/update something and use that in the next query, do a save first. That has been one of my most common mistakes…
I did not treat the problem of lazy loading in this article as it’s outside the scope but when you start working with EF and use a lot of Navigation Properties keep this problem in mind.
Entity Framework context per request的更多相关文章
- Entity Framework Context上下文管理(CallContext 数据槽)
Context上下文管理 Q1:脏数据 Q2:一次逻辑操作中,会多次访问数据库,增加了数据库服务器的压力 >在一次逻辑操作中实现上下文实例唯一 方法一:单例模式:内存的爆炸式增长 在整个运行期间 ...
- 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表
创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...
- Entity Framework在Asp.net MVC中的实现One Context Per Request(附源码)
上篇中"Entity Framework中的Identity map和Unit of Work模式", 由于EF中的Identity map和Unit of Work模式,EF体现 ...
- Entity Framework在Asp.net MVC中的实现One Context Per Request(转)
上篇中"Entity Framework中的Identity map和Unit of Work模式", 由于EF中的Identity map和Unit of Work模式,EF体现 ...
- Entity Framework : The model backing the '' context has changed since the database was created
1.采用code first 做项目时,数据库已经生成,后期修改数据库表结构.再次运行时出现一下问题: Entity Framework : The model backing the '' cont ...
- Entity Framework 6如何进行导航属性的筛选(context.Msg.First(t=>t.Id==1).Include(t=>t.MsgDetail),筛选MsgDetail带条件)
问题: https://q.cnblogs.com/q/98333/ Msg表(Id,Content,IsDel).内有 virtual ICollection<MsgDetail> Ms ...
- [转]Entity Framework Refresh context?
本文转自:http://stackoverflow.com/questions/20270599/entity-framework-refresh-context If you want to rel ...
- Entity Framework 6 Recipes 2nd Edition(9-1)译->用Web Api更新单独分离的实体
第九章 在N层结构的应用程序中使用EF 不是所有的应用都能完全地写入到一个单个的过程中(就是驻留在一个单一的物理层中),实际上,在当今不断发展的网络世界,大量的应用程序的结构包含经典的表现层,应用程, ...
- Entity Framework 6 Recipes 2nd Edition(9-3)译->找出Web API中发生了什么变化
9-3. 找出Web API中发生了什么变化 问题 想通过基于REST的Web API服务对数据库进行插入,删除和修改对象图,而不必为每个实体类编写单独的更新方法. 此外, 用EF6的Code Fri ...
随机推荐
- SAP事件 Event Flow(转载)
1 报表过程事件 报表过程事件是在报表运行过程中由系统自动控制,按照一定次序被触发的事件,其目的是从数据库中选择数据并整理,准备进行列表输出.这些事件从报表程序启动开始就被系统顺序触发,现分述如下: ...
- python虚拟环境 virtualenv工具
为了隔离各类环境,保证环境间不冲突,python中存在虚拟环境,可以在一个文件夹里生成相应的环境,防止与python自带环境冲突 首先我们下载virtualenv,若你未安装python,应到pyth ...
- 用PHP和Python生成短链接服务的字符串ID
假设你想做一个像微博短链接那样的短链接服务,短链接服务生成的URL都非常短例如: http://t.cn/E70Piib, 我们应该都能想到链接中的E70Piib对应的就是存储长链接地址的数据记录的I ...
- JavaScript 日期权威指南
简介 JavaScript通过强大的对象为我们提供日期处理功能:日期. 本文确实_不是_谈论 Moment.js ,我认为它是处理日期的最佳库,你应该在处理日期时几乎总是使用它. Date对象 Dat ...
- python面向对象之反射和内置方法
一.静态方法(staticmethod)和类方法(classmethod) 类方法:有个默认参数cls,并且可以直接用类名去调用,可以与类属性交互(也就是可以使用类属性) 静态方法:让类里的方法直接被 ...
- Base64及其Python实现
1. 什么是Base64 Base64是一种基于64个可打印字符来表示二进制数据的表示方法 Base64是一种编码方式,提及编码方式,必然有其对应的字符集合.在Base64编码中,相互映射的两个集合是 ...
- ARM Linux内核源码剖析索引
start_kernel -->asm-offset.h 生成 -->proc_info_list -->machine_desc -->__vet_atags --> ...
- makefile学习(2)
新建目录如下: ├─include │ integrate.h │ └─src │ integrate.c │ main.c │ makefile │ └─obj obj用于存放object文件. m ...
- Logistic回归python实现小样例
假设现在有一些点,我们用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作回归.利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,依次进行分类.Lo ...
- loj2062 [HAOI2016]地图
ref #include <algorithm> #include <iostream> #include <cstdio> #include <cmath& ...