以下这样的一个lamda查询语句,不会立马去查询数据库,只有当须要用时去调用(如取某行,取某个字段、聚合),才会去操做数据库,EF中自己的查询方法返回的都是IQueryable接口。html
连接:IEnumerable和IQueryable接口说明sql
其中聚合函数会影响数据加载,诸如:toList(),sum(),Count(),First()能使数据当即查询加载。数据库
通常状况,咱们都是使用ToList或First来完成预先加载数据操做。但在EF中还可使用Load() 方法来显式加载,将获取的数据放到EF Context中,缓存起来备用。和ToList()很像,只是它不建立列表只是把数据缓存到EF Context中而已,开销较少。缓存
using (var context = new TestDB()) { context.Place.Where(t=>t.PlaceID==9).Load(); }
VS中的方法说明:并发
用以前的Place类和People为例分布式
Place对象以下:函数
public class Place { [Key] public int PlaceID { get; set;} public string Provice { get; set; } public string City { get; set; } //导航属性 public virtual List<People> Population { get; set; } }
下面查询,不会主动去查询出导航属性(Population )关联的数据spa
using (var context = new TestDB()) { var obj = context.Place.Where(t => t.PlaceID == 9).FirstOrDefault(); }
能够看到Population为null线程
只有用到Population对象时,EF才会发起到数据库的查询;code
固然导航数据必须标记virtual,配置延迟加载
//导航属性 public virtual Place Place { get; set; }
要注意的事:在延迟加载条件下,常常觉得导航数据也加载了,从而在循环中去遍历导航属性,形成屡次访问数据库。
除了前面所说的,使用聚合函数(sum等)外来当即预加载数据,还可使用Include方法
在上面的查询中,想要查询place以及关联的Population数据以下:
using (var context = new TestDB()) { var obj = context.Place.Where(t => t.PlaceID == 9).Include(p=>p.Population).FirstOrDefault(); }
在EF中,saveChanges()默认是开启了事务的,在调用saveChanges()以前,全部的操做都在同一个事务中,同一次数据库链接。若使用同一DbContext对象,EF的默认事务处理机制基本知足使用。
除此以外,如下两种状况怎么使用事务:
using (var context = new TestDB()) { using (var tran=context.Database.BeginTransaction()) { try { context.Place.Add(new Place { City = "beijing", PlaceID = 11 }); context.SaveChanges(); context.People.Add(new People { Name = "xiaoli" }); context.SaveChanges(); tran.Commit(); } catch (Exception) { tran.Rollback(); } } }
注意的是,不调用commit()提交,没有异常事务也不会默认提交。
using (var tran = new TransactionScope()) { try { using (var context = new TestDB()) { context.Place.Add(new Place { City = "5555"}); context.SaveChanges(); } using (var context2 = new TestDB2()) { context2.Student.Add(new Student { Name="li"}); context2.SaveChanges(); } throw new Exception(); tran.Complete(); } catch (Exception) { } }
注意:上面代码在同一个事务内使用了多个DBcontext,会造次屡次链接关闭数据库
如是多个DBcontext连着是同一个数据库的话,能够将一个己打开的数据库链接对象传给它,而且须要指定EF在DbContext对象销毁时不关闭数据库链接。避免形成屡次链接关闭数据库
DbContext对象改造,增长重载构造函数;;传入两个参数
public class TestDB2 : DbContext { public TestDB2():base("name=Test")
{ } public TestDB2(DbConnection conn, bool contextOwnsConnection) : base(conn, contextOwnsConnection) { } public DbSet<Student> Student { get; set; } }
事务代码以下:
using (TransactionScope scope = new TransactionScope()) { String connStr = ……; using (var conn = SqlConnection(connStr)) { try { conn.Open(); using (var context1 = new MyDbContext(conn, contextOwnsConnection: false)) { …… context1.SaveChanges(); } using (var context2 = new MyDbContext(conn, contextOwnsConnection: false)) { …… context2.SaveChanges(); }
scope.Complete(); } catch (Exception e) { } finally { conn.Close(); } } }
在实际场景中,并发是很常见的事,同条记录同时被不一样的两个用户修改
在EF中有两种常见的并发冲突检测
能够指定对象的一个或多个属性用于并发检测,在对应属性加上ConcurrencyCheck特性
这里咱们指定Student 对象的属性Name
public class Student { [Key] public int ID { get; set; } [ConcurrencyCheck] public string Name { get; set; } public int Age { get; set; } }
用个两个线程同时去更新Student对象,模拟用户并发操做
static void Main(string[] args) { Task t1 = Task.Run(() => { using (var context = new TestDB2()) { var obj = context.Student.First(); obj.Name = "LiMing"; context.SaveChanges(); } }); Task t2 = Task.Run(() => { using (var context = new TestDB2()) { var obj = context.Student.First(); obj.Age = 26; context.SaveChanges(); } }); Task.WaitAll(t1,t2); }
并发冲突报错:
查看了sql server profiler,发现加了[ConcurrencyCheck]的属性名和值将出如今Where子句中
exec sp_executesql N'UPDATE [dbo].[Students] SET [Age] = @0 WHERE (([ID] = @1) AND ([Name] = @2)) ',N'@0 int,@1 int,@2 nvarchar(max) ',@0=26,@1=1,@2=N'WANG'
很显然:
t2再修改Age,根据并发检测属性Name的值已被改变,有其余用户在修改同一条数据,并发冲突。
为每一个实体类都单独地设定检测属性实在太麻烦,应该由数据库来设定特殊字段值并维护更新会更好,下面就是另外一种方法
建立一个基类Base,指定一个特殊属性值,SQL Server中相应的字段类型为timestamp,本身项目中的实体类均可以继承它,
public class Base { [Timestamp] public byte[] RowVersion { get; set; } }
Student先基础base类,每次更新Student数据,RowVersion 字段就会由数据库生成一个新的值,根据这个特殊字段来检测并发冲突;实体类再也不去考虑设置那个属性值和更新。
同时更新并发,EF会抛出:DbUpdateConcurrencyException
两个更新线程如上:t1和t2
处理一
Task t1 = Task.Run(() => { using (var context = new TestDB()) { try { var obj = context.Student.First(); obj.Name = "LiMing2"; context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { //从数据库从新加载数据并覆盖当前保存失败的对象 ex.Entries.Single().Reload(); context.SaveChanges(); } } });
也就是说,t1并发冲突更新失败,会从新从数据库拉取对象覆盖当前失败的对象,t1本来的更新被做废,于此同时的其余用户并发操做,如t2的更新将会被保存下来
处理二
Task t1 = Task.Run(() => { using (var context = new TestDB()) { try { var obj = context.Student.First(); obj.Name = "LiMing2"; context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { var entry = ex.Entries.Single(); entry.OriginalValues.SetValues(entry.GetDatabaseValues()); context.SaveChanges(); } } });
从数据库从新获取值来替换保存失败的对象的属性原始值,再次提交更改,数据库就不会由于当前更新操做获取的原始值与数据库里现有值不一样而产生异常(如检测属性的值已成同样),t1的更新操做就能顺利提交,其余并发操做如t2被覆盖