Entity Framework 查漏补缺 (二)

数据加载

如下这样的一个lamda查询语句,不会立马去查询数据库,只有当需要用时去调用(如取某行,取某个字段、聚合),才会去操作数据库,EF中本身的查询方法返回的都是IQueryable接口。

链接:IEnumerable和IQueryable接口说明

其中聚合函数会影响数据加载,诸如:toList(),sum(),Count(),First()能使数据立即查询加载。

IQueryable中的Load方法

一般情况,我们都是使用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 )关联的数据

using (var context = new TestDB())
{
    var obj = context.Place.Where(t => t.PlaceID == 9).FirstOrDefault();
}

可以看到Population为null

只有用到Population对象时,EF才会发起到数据库的查询;

当然导航数据必须标记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的默认事务处理机制基本满足使用。

除此之外,以下两种情况怎么使用事务:

  1. 数据分阶段保存,多次调用saveChanges()
  2. 使用多个DbContext对象(尽量避免)

第一种情况:显式事务

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()提交,没有异常事务也不会默认提交。

第二种情况:TransactionScope分布式事务

  • 引入System.Transactions.dll
  • Windows需要开启MSDTC
  • TransactionScope也于适用于第一种情况。这里只讨论连接多个DBcontext的事务使用
  • 需要调用Complete(),否则事务不会提交
  • 在事务内,报错会自动回滚
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对象改造,增加重载构造函数;;传入两个参数

  • 数据库连接DbConnection
  • contextOwnsConnection=false(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();
        }
    }
}

DBcontent线程内唯一

链接:dbcontext实例创建问题

并发

在实际场景中,并发是很常见的事,同条记录同时被不同的两个用户修改

在EF中有两种常见的并发冲突检测

方法一:ConcurrencyCheck特性

可以指定对象的一个或多个属性用于并发检测,在对应属性加上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的值已被改变,有其他用户在修改同一条数据,并发冲突。

为每个实体类都单独地设定检测属性实在太麻烦,应该由数据库来设定特殊字段值并维护更新会更好,下面就是另一种方法

方法二:timestamp

创建一个基类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被覆盖

原文地址:https://www.cnblogs.com/qiuguochao/p/10162291.html

时间: 2024-10-14 23:42:49

Entity Framework 查漏补缺 (二)的相关文章

Entity Framework 查漏补缺 (一)

明确EF建立的数据库和对象之间的关系 EF也是一种ORM技术框架, 将对象模型和关系型数据库的数据结构对应起来,开发人员不在利用sql去操作数据相关结构和数据.以下是EF建立的数据库和对象之间关系 关系数据库 对象 数据库 DbContext类 表 DbContext中的DbSet<实体类名> 表间的关联 实体类之间的关联 字段 实体类的公有属性 单条数据 单个实体类的对象 约束(主键.外键默认值) 实体类中的特性 了解EDM( 实体数据模型) EF使用概念模型. 映射和存储模型.三个模型来描

Entity Framework 查漏补缺 (三)

Code First的数据库映射 有两种方式来实现数据库映射: 数据属性:Data Annotation 映射配置: Fluent API 有继承关系的实体如何映射? Code First在生成数据库表时,默认使用TPH方式 就是把父类和子类生成同一张表,额外增加了一列Discriminator字段,区分是父类或子类的数据类型 比如: 父类Book对象 public class Book { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Ide

java知识查漏补缺

一.重写(override)和重载(overload)的区别 二者除了名字相似,其实没什么联系 范围不同:重写发生在同一个类的不同方法之间.重载发生在父类和子类自荐. 前提: 重写要求:方法名相同,参数列表不同,对于返回值类型不要求相同. 重载要求:方法名形同,参数列表也相同.重载是实现多态的关键,注意如果父类中的方法是private类型,那么子类中对应方法不算重载,而相当于是定义了一个新方法. 二.final的用法 修饰类:该类不能被继承 修饰方法:该方法不能被重写 修饰属性:该属性初始化后不

2019/5/12 查漏补缺

目录 2019/5/12 查漏补缺 数据类型分为两大类:基本类型和引用类型: java中类的继承关系 关于接口 重载和重写 静态变量 java中的关键字和保留字 数据库操作 实现数据库收回部分权限的操作 关于数据库错误类型 SQL四种语言: 数据库常见的四种故障 数据库设计六个阶段: Java的异常分为两种 外模式,内模式 数据库范式 语句null的书写 2019/5/12 查漏补缺 数据类型分为两大类:基本类型和引用类型: 基本类型只能保存一些常量数据,引用类型除了可以保存数据,还能提供操作这

近来的java小总结(2.1):类的知识的查漏补缺

首先,我是一名新手,所以,要带着批判的眼光来看下面的文章   这篇文章说了些什么? 这文章是我近来8.6号来在编程思想上打的代码,从0~200页的源码接近到在这里,下文正是总结这0~200页的的知识,涉及到接口,内部类.初始化,数值计算的一些细节.此文章不会一下子写完,可能隔一天可能再补下来.因为代码确实有点多.. 注意 1 我的注释不一定正确(不过各小标题和代码一定是正确的,因为是书本上的原话,但是注释不一定正确),如果你确信我的内容的话,你可能会损失很大,因为我只是个菜鸟,我只是来补救一些知

查漏补缺——java多态

---恢复内容开始--- 刚学完java,开始了查漏补缺阶段阶段,为了巩固自己的知识和为别人提供一些微末的帮助决定开通博客,求各位大牛们指出我的不足,不要吝惜言语,也希望我的总结可以对别人有帮助,对自己对他人负责. 开始正文:术语多态:可以定义为“有多种形态”,多态引用是一个一个在不同时刻可以指向不同类型对象的引用变量.通过多态引用可以调用不同的具体的方法. 类方法的多态性的实现有两种方式: 1:方法重载:可以声明多个同名但是参数不同(个数.类型和顺序)的方法.注意呵呵重载方法只能声明在一个类里

近来的java小总结(2.2):类的知识的查漏补缺

1 首先,我是一名新手,所以,要带着批判的眼光来看下面的文章   这篇文章说了些什么? 这文章是我近来8.6号来在编程思想上打的代码,从0~200页的源码接近到在这里,下文正是总结这0~200页的的知识,涉及到接口,内部类.初始化,数值计算的一些细节.此文章不会一下子写完,可能隔一天可能再补下来.因为代码确实有点多.. 注意 1 我的注释不一定正确(不过各小标题和代码一定是正确的,因为是书本上的原话,但是注释不一定正确),如果你确信我的内容的话,你可能会损失很大,因为我只是个菜鸟,我只是来补救一

自家用的java小总结(2.4):类的知识的查漏补缺(内部类)

1 2      首先,这是一篇自己用的文章,不对读者承担任何责任,所以,要带着批判的眼光来看下面的文章   1 发现了,得加上输出结果,怕自己出错,~~   这篇文章说了些什么? 这文章是我近来8.6号来在编程思想上打的代码,从0~200页的源码接近到在这里,下文正是总结这0~200页的的知识,涉及到接口,内部类.初始化,数值计算的一些细节.此文章不会一下子写完,可能隔一天可能再补下来.因为代码确实有点多.. 注意 1 我的注释不一定正确(不过各小标题和代码一定是正确的,因为是书本上的原话,但

查漏补缺

查漏补缺[1].this语句:this语句用于构造函数之间进行相互调用.this语句只能定义在构造函数的第一行,因为初始化要先执行.[2].对象的初始化过程 Person p =new Person("hei",10); 1.因为new用到了Person.class,所以先找到Person.class文件加载到内存中 2.执行类中的静态代码块,如果有的话,给Person.class类进行初始化 3.在堆内存中开辟空间,分配内存地址 4.在堆内存中建立对象的特有属性,并进行默认初始化 5