Code First开发系列之管理并发和事务(转)

转自:http://www.cnblogs.com/farb/p/ConcurrencyAndTransctionManagement.html

返回《8天掌握EF的Code First开发》总目录


本篇目录



本系列的源码本人已托管于Coding上:点击查看,想要注册Coding的可以点击该连接注册
先附上codeplex上EF的源码:entityframework.codeplex.com,此外,本人的实验环境是VS 2013 Update 5,windows 10,MSSQL Server 2008/2012。

理解并发

并发管理解决的是允许多个实体同时更新,实际上这意味着允许同时在相同的数据上执行多个数据库操作。并发是在一个数据库上管理多个操作的一种方式,同时遵守了数据库操作的ACID属性(原子性,一致性,隔离性和持久性)

想象一下下面几种可能发生并发的场景:

  • 用户甲和乙都尝试修改相同的实体
  • 用户甲和乙都尝试删除相同的实体
  • 用户甲正在尝试修改一个实体时,用户乙已经删除了该实体
  • 用户甲已经请求读取一个实体,用户乙读完该实体之后更新了它

这些场景可能会潜在地产生错误的数据,试想,成百上千的用户同时尝试操作一个相同的实体,这种并发问题将会对系统带来更大的影响。

在处理与并发相关的问题时,一般有两种方法:

  • 积极并发:无论何时从数据库请求数据,数据都会被读取并保存到应用内存中。数据库级别没有放置任何显式锁。数据操作会按照数据层接收到的顺序执行。
  • 消极并发:无论何时从数据库请求数据,数据都会被读取,然后该数据上就会加锁,因此没有人能访问该数据。这会降低并发相关问题的机会,缺点是加锁是一个昂贵的操作,会降低整个应用程序的性能。EF默认支持积极并发,这样,一旦所有的数据被读取,就会呈现在内存中。当然,也可以配置EF使用消极并发,但是EF不直接支持。

理解积极并发

前面提到,在积极并发中,无论何时从数据库请求数据,数据都会被读取并保存到应用内存中。数据库级别没有放置任何显式锁。因为这种方法没有添加显式锁,所以比消极并发更具扩展性和灵活性。使用积极并发,重点是如果发生了任何冲突,应用程序要亲自处理它们。最重要的是,使用积极并发控制时,在应用中要有一个冲突处理策略,要让应用程序的用户知道他们的修改是否因为冲突的缘故没有持久化。积极并发本质上是允许冲突发生,然后以一种适当的方式解决该冲突。

下面是处理冲突的策略例子。

忽略冲突/强制更新

这种策略是让所有的用户更改相同的数据集,然后所有的修改都会经过数据库,这就意味着数据库会显示最后一次更新的值。这种策略会导致潜在的数据丢失,因为许多用户的更改都丢失了,只有最后一个用户的更改是可见的。

部分更新

在这种情况中,我们也允许所有的更改,但是不会更新完整的行,只有特定用户拥有的列更新了。这就意味着,如果两个用户更新相同的记录但却不同的列,那么这两个更新都会成功,而且来自这两个用户的更改都是可见的。

警告/询问用户

当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被别人修改了,这时应用程序就会警告该用户该数据已经被某人更改了,然后询问他是否仍然要重写该数据还是首先检查已经更新的数据。

拒绝更改

当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被别人修改了,此时告诉该用户不允许更新该数据,因为数据已经被某人更新了。

理解消极并发

和积极并发相反,消极并发的目标是永远不让任何冲突发生。这是通过在使用记录之前就在记录上放置显式锁实现的。数据库记录上可以得到两种类型的锁:

  • 只读锁
  • 更新锁

当把只读锁放到记录上时,应用程序只能读取该记录。如果应用程序要更新该记录,它必须获取到该记录上的更新锁。如果记录上加了只读锁,那么该记录仍然能够被想要只读锁的请求使用。然而,如果需要更新锁,该请求必须等到所有的只读锁释放。同样,如果记录上加了更新锁,那么其他的请求不能再在这个记录上加锁,该请求必须等到已存在的更新锁释放才能加锁。

从前面的描述中,似乎消极并发能解决所有跟并发相关的问题,因为我们不必在应用中处理这些问题。然而,事实上并不是这样的。在使用消极并发管理之前,我们需要记住,使用消极并发有很多问题和开销。下面是使用消极并发面临的一些问题:

  • 应用程序必须管理每个操作正在获取的所有锁;
  • 加锁机制的内存需求会降低应用性能

多个请求互相等待需要的锁,会增加死锁的可能性。由于这些原因,EF不直接支持消极并发。如果想使用消极并发的话,我们可以自定义数据库访问代码。此外,当使用消极并发时,LINQ to Entities不会正确工作。

我们尽可能不要尝试使用消极并发。并发相关的冲突可以使用TimeStamp字段或者RowVersion类型处理。后面会做介绍。

使用EF实现积极并发

使用EF实现积极并发有很多方法,接下来我们就会看一下这些方法。我这里仍然使用打赏者的例子。
新建一个控制台项目,取名ConcurrencyAndTransactionManagement,这次只创建打赏者实体类如下:

public class Donator
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Amount { get; set; }
    public DateTime DonateDate { get; set; }
}

EF的默认并发

先看一下EF默认是如何处理并发的。现在假设我们的应用程序要更新一个Donator的Amount值,那么我们首先需要实现这两个函数GetDonator() 和 UpdateDonator(),前者用于获取指定Donator,后者用于更新指定Donator。

static Donator GetDonator(int id)
{
    using (var db=new Context())
    {
        return db.Donators.Find(id);
    }
}

static void UpdateDonator(Donator donator)
{
    using (var db=new Context())
    {
        db.Entry(donator).State=EntityState.Modified;
        db.SaveChanges();
    }
}

下面我们实现这么一种场景:有两个用户甲和乙都读取了同一个Donator实体,然后这两个用户都尝试更新这个实体的不同字段,比如甲更新Name字段,乙更新Amount字段,代码如下:

//1.用户甲获取id=1的打赏者
var donator1 = GetDonator(1);
//2.用户乙也获取id=1的打赏者
var donator2 = GetDonator(1);
//3.用户甲只更新这个实体的Name字段
donator1.Name = "用户甲";
UpdateDonator(donator1);
//4.用户乙只更新这个实体的Amount字段
donator2.Amount = 100m;
UpdateDonator(donator2);

上面的代码尝试模拟了一种并发问题。现在,两个用户都有相同的数据副本,然后尝试更新相同的记录。执行代码前,先看下数据库中的数据:

为了测试,在执行第四步时打一个断点:

在断点之后的代码执行之前,去数据库看一下数据,可以看到用户甲的更新已经产生作用了:

继续执行代码,再看一下数据库中的数据发生了什么变化:

从上面的截图可以看出,用户乙的请求成功了,而用户甲的更新丢失了。因此,从上面的代码不难看出,如果我们使用EF更新整条记录,那么最后一个请求总会获取胜利,也就是说,最后一次请求的更新会覆盖之前所有请求的更新。

设计处理字段级别并发的应用

接下来,我们会看到如何编写处理字段级并发问题的应用代码。这种方式设计应用的思想是,只有更新的字段会在数据库中更改。这个就保证了如果多个用户正在更新不同的字段,所有的更改都会持久化到数据库。

实现这个的关键是让该应用识别用户正在请求更新的所有列,然后为该用户有选择地更新那些字段。通过以下两个东西来实现:

  • 一个方法:该方法会给我们一个原始模型的克隆,只有用户请求的属性会更新为新值
  • 更新方法:它会检查原始请求模型的哪个属性值已经更改,然后在数据库中只更新那些值。

因此,首先需要创建一个简单的方法,该方法需要模型属性的值,然后会返回一个新的模型,该模型除了用户尝试更新的属性之外,其他的属性值都和原来的模型属性值相同。

static Donator GetUpdatedDonator(int id,string name,decimal amount,DateTime donateDate)
{
    return new Donator
    {
        Id = id,
        Name = name,
        Amount = amount,
        DonateDate = donateDate
    };
}

如果用户只想更新Amount字段,方法的调用就像下面这样:

var donator1 = GetDonator(1);
var donator2 = GetDonator(1);
var newDonator = GetUpdatedDonator(donator2.Id, donator1.Name,100m, donator1.DonateDate);

在上面的代码中,donator1是用户请求模型的原始对象,100m是打赏金额的新值。

上面的方法超级简单,它只显示了如何获得具有更新属性值的克隆对象。现实生活中,很少会看到这样的代码。为了更简洁,我们还可以使用映射模块将领域模型映射到数据模型。

下一步,需要更改更新方法。该更新方法会实现下面更新数据的算法:

  1. 从数据库中检索最新的模型值
  2. 检查原始模型和要更新的模型来找出更改属性的列表
  3. 只更新步骤1中检索到的模型发生变化的属性
  4. 保存更改

该算法的代码大概像下面这个样子:

static void UpdateDonatorEnhanced(Donator originalDonator,Donator newDonator)
{
    using (var db=new Context())
    {
        //从数据库中检索最新的模型
        var donator = db.Donators.Find(originalDonator.Id);
        //接下来检查用户修改的每个属性
        if (originalDonator.Name!=newDonator.Name )
        {
            //将新值更新到数据库
            donator.Name = newDonator.Name;
        }
        if (originalDonator.Amount != newDonator.Amount)
        {
            //将新值更新到数据库
            donator.Amount = newDonator.Amount;
        }
        //这里省略其他属性...
        db.SaveChanges();
    }
}

接下来,使用这两个方法来更新应用程序代码,并检查结果:

 #region 2.0   设计处理字段级别的并发应用
 //1.用户甲读取id=1的打赏者
 var donator1 = GetDonator(1);
 //2.用户乙同样读取id=1的打赏者
 var donator2 = GetDonator(1);
 //3.用户甲通过创建一个新的对象来更新打赏金额为100m
 var newDonator1 = GetUpdatedDonator(donator2.Id, donator1.Name,100m, donator1.DonateDate);
UpdateDonatorEnhanced(donator1,newDonator1);
 //4.用户乙通过创建一个新的对象来更新打赏者姓名为“并发测试”
 var newDonator2 = GetUpdatedDonator(donator2.Id, "并发测试", donator2.Amount, donator2.DonateDate);
 UpdateDonatorEnhanced(donator1, newDonator2);
 #endregion

运行代码之前,先看下数据库中的数据:

在执行第四步时打个断点,运行程序:

再次查看数据库中的数据,发现用户甲的操作已经执行了:

继续运行程序,再次查看数据库的数据,发现用户乙的操作也执行了:

从上面的截图看到,两个用户的请求同一个实体的更新值都持久化到数据库中了。因此,如果用户更新不同的字段,该程序可以有效地处理并发更新了。但是如果多个用户同时更新相同的字段,那么这种方法仍然显示的是最后一次请求的值。虽然这种方式减少了一些并发相关的问题,但是这种方法意味着我们必须写大量代码来处理并发问题。后面我们会看到如何使用EF提供的机制来处理并发问题。

为并发实现RowVersion

前面,我们看到了EF默认如何处理并发(最后一次请求获胜),然后看了如果多个用户尝试更新不同的字段时,如何设计应用处理这些问题。接下来,我们看一下当多个用户更新相同的字段时,使用EF如何处理字段级并发。

EF让我们指定字段级并发,这样如果一个用户更新一个字段的同时,该字段已经被其他人更新过了,就会抛出一个并发相关的异常。使用这种方法,当多个用户尝试更新相同的字段时,我们就可以更有效地处理并发相关的问题。

如果我们为多个字段使用了特定字段的并发,那么会降低应用性能,因为生成的Sql会更大,更加有效的方式就是使用RowVersion机制。RowVersion机制使用了一种数据库功能,每当更新行的时候,就会创建一个新的行值。

给Donator实体添加一个属性:

[Timestamp]
public byte[] RowVersion { get; set; }

//修改上下文

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Donator>().Property(d => d.RowVersion).IsRowVersion();
    base.OnModelCreating(modelBuilder);
}

添加一个数据库初始化器,重新生成数据库,数据库模式变为:

插入两条数据,RowVersion列显示的是二进制数据

现在,EF就会为并发控制追踪RowVersion列值。接下来尝试更新不同的列:

 //1.用户甲获取id=1的打赏者
var donator1 = GetDonator(1);
//2.用户乙也获取id=1的打赏者
var donator2 = GetDonator(1);
//3.用户甲只更新这个实体的Name字段
donator1.Name = "用户甲";
UpdateDonator(donator1);
//4.用户乙只更新这个实体的Amount字段
donator2.Amount = 100m;
UpdateDonator(donator2); 

 

运行程序,会抛出下面的异常:

其他信息:Entities may have been modified or deleted since entities were loaded.

从抛出的异常信息来看,很明显是抛出了和并发相关的异常DbUpdateConcurrencyException,其他信息说明了自从实体加载以来,可能已经被修改或删除了

无论何时一个用户尝试更新一条已经被其他用户更新的记录,都会获得异常DbUpdateConcurrencyException

当实现并发时,我们总要编写异常处理的代码,给用户展示一个更友好的描述信息。比如:

  //4.用户乙只更新这个实体的Amount字段
 try
 {
     donator2.Amount = 100m;
     UpdateDonator(donator2);
     Console.WriteLine("应该发生并发异常!");
 }
 catch (DbUpdateConcurrencyException ex)
 {
     Console.WriteLine("异常如愿发生!");
 }

此时,我们应该使用当前的数据库值更新数据,然后重新更改。作为开发者,如果我们想要协助用户的话,可以使用EF的DbEntityEntry类获取当前的数据库值。

理解事务

处理以数据为中心的应用时,另一个重要的话题是事务管理。ADO.NET为事务管理提供了一个非常干净和有效的API。因为EF运行在ADO.NET之上,所以EF可以使用ADO.NET的事务管理功能。

当从数据库角度谈论事务时,它意味着一系列操作被当作一个不可分割的操作。所有的操作要么全部成功,要么全部失败。事务的概念是一个可靠的工作单元,事务中的所有数据库操作应该应该被看作一个工作单元。

从应用程序的角度来看,如果我们有多个数据库操作被当作一个工作单元,那么应该将这些操作包裹在一个事务中。为了能够使用事务,应用程序需要执行下面的步骤:

  1. 开始事务;
  2. 执行所有的查询,执行所有的数据库操作,这些操作被视为一个工作单元;
  3. 如果所有的事务成功了,那么提交事务;
  4. 如果任何一个操作失败,就回滚事务。

创建测试环境

提到事务,最经典的例子莫过于银行转账了。我们这里也使用这个例子来理解一下和事务相关的概念。为了简单模拟银行转账的情景,假设银行为不同的账户类型使用了不同的表,对应地,我创建了两个实体OutputAccountInputAccount,实体类代码如下:

 [Table("OutputAccounts")]
 public class OutputAccount
 {
     public int Id { get; set; }
     [StringLength(8)]
     public string Name { get; set; }
     public decimal Balance { get; set; }
 }

[Table("InputAccounts")]
public class InputAccount
{
    public int Id { get; set; }
    [StringLength(8)]
    public string Name { get; set; }
    public decimal Balance { get; set; }
}

从应用程序的角度看,无论何时用户将钱从OutputAccount转入InputAccount,这个操作应该被视为一个工作单元。永远不应该发生OutputAccount的金额扣除了,而InputAccount的金额没有增加!接下来我们就看一下使用EF如何管理事务。

现在给数据库插入数据,让它们的初始金额如下所示:

现在,我们尝试使用EF的事务从OutputAccount的甲转入1000给InputAccount的乙。

这只是个例子,只为了以一种简单的方式描述涉及到的概念。现实生活中,这个场景的数据库会远比这个更复杂和更优化。

EF的默认事务处理

EF的默认行为是,无论何时执行任何涉及Create,Update或Delete的查询,都会默认创建事务。当DbContext类上的SaveChanges()方法被调用时,事务就会提交。

要实现我们的场景,代码应该是下面这样的:

#region 4.0 EF默认的事务处理

int outputId = 2,inputId=1;
decimal transferAmount = 1000m;
using (var db=new Context())
{
    //1 检索事务中涉及的账户
    var outputAccount = db.OutputAccounts.Find(outputId);
    var inputAccount = db.InputAccounts.Find(inputId);
    //2 从输出账户上扣除1000
    outputAccount.Balance -= transferAmount;
    //3 从输入账户上增加1000
    inputAccount.Balance += transferAmount;

    //4 提交事务
    db.SaveChanges();
}
#endregion

运行程序,结果如下:

可以看到,甲账户上少了1000,而乙账户上多了1000。因此,这两个操作有效地被包裹在了一个事务中,并作为一个工作单元执行。如果任何一个操作失败,数据就不会发生变化。

因为把操作放到事务中没有好处,但是却降低了整个应用程序的性能,因此,EF不会对涉及数据库的Select查询使用事务。

使用TransactionScope处理事务

如果有一个场景具有多个DbContext对象,那么我们想将涉及多个DbContext对象的操作关联为一个工作单元,这时,我们需要在TransactionScope对象内部包裹SaveChanges方法的调用。为了描述这个场景,我们使用DbContext类的两个不同实例来执行扣款和收款:

 #region 5.0  使用TransactionScope处理事务
 int outputId = 2, inputId = 1;
 decimal transferAmount = 1000m;
 using (var ts=new TransactionScope(TransactionScopeOption.Required))
 {
     var db1=new Context();
     var db2=new Context();
     //1 检索事务中涉及的账户
     var outputAccount = db1.OutputAccounts.Find(outputId);
     var inputAccount = db2.InputAccounts.Find(inputId);
     //2 从输出账户上扣除1000
     outputAccount.Balance -= transferAmount;
     //3 从输入账户上增加1000
     inputAccount.Balance += transferAmount;

     db1.SaveChanges();
     db2.SaveChanges();

     ts.Complete();
 }
 #endregion

上面的代码中,我们使用了两个不同的DbContext实例执行扣款和收款操作。因此,默认的EF行为不会工作。在调用各自的SaveChanges()方法时,和上下文相关的各个事务不会提交。相反,因为它们都在TransactionScope对象的内部,所以,当TransactionScope对象的Complete()方法调用时,事务才会提交。如果任何一个操作失败,就会发生异常,TransactionScope就不会调用Complete()方法,从而回滚更改。

使用EF6管理事务

从EF 6起,EF在DbContext对象上提供了Database.BeginTransaction()方法,当使用上下文类在事务中执行原生SQL命令时,这个方法特别有用。

接下来看一下如何使用这个新方法管理事务。这里我们使用原生SQL从OutputAccounts中扣款,使用模型类给InputAccounts收款:

 #region 6.0 使用EF6管理事务
 int outputId = 2, inputId = 1;
 decimal transferAmount = 1000m;
 using (var db=new Context())
 {
     using (var trans=db.Database.BeginTransaction())
     {
         try
         {
             var sql = "Update OutputAccounts set [email protected] where [email protected]";
             db.Database.ExecuteSqlCommand(sql, new SqlParameter("@amountToDebit", transferAmount), new SqlParameter("@outputId",outputId));

             var inputAccount = db.InputAccounts.Find(inputId);
             inputAccount.Balance += transferAmount;
             db.SaveChanges();

             trans.Commit();
         }
         catch (Exception ex)
         {
             trans.Rollback();
         }
     }
 }
 #endregion

稍作解释,首先创建了一个DbContext类的实例,然后使用这个实例通过调用Database.BeginTransaction()方法开始了一个事务。该方法给我们返回了一个DbContextTransaction对象的句柄,使用该句柄可以提交或者回滚事务。然后使用原生SQL从OutputAccounts中扣款,使用模型类为 InputAccounts收款。调用SaveChanges()方法只会影响第二个操作(在事务提交之后影响),但不会提交事务。如果两个操作都成功了,那么就调用DbContextTransaction对象的Commit()方法,否则,我们就处理异常并调用Rollback()方法回滚事务。

这种方式只用于EF6,如果是EF6之前的版本,必须依赖TransactionScope管理事务。

使用已存在的事务

有时,我们想在EF的DbContext类中使用一个已存在的事务。原因可能有这么几个:

  • 一些操作可能在应用的不同部分完成。
  • 对老项目使用了EF,并且这个老项目使用了一个类库,这个类库给我们提供了事务或数据库连接的句柄。

对于这些场景,EF允许我们在DbContext类中使用一个和事务相关联的已存在连接。接下来,写一个简单的函数来模拟老项目的类库提供句柄,该函数使用纯粹的ADO.NET执行扣款操作:

//模拟老项目的类库
static bool DebitOutputAccount(SqlConnection conn, SqlTransaction trans, int accountId, decimal amountToDebit)
{
    int affectedRows = 0;
    var command = conn.CreateCommand();
    command.Transaction = trans;
    command.CommandType=CommandType.Text;
    command.CommandText = "Update OutputAccounts set [email protected] where [email protected]";
    command.Parameters.AddRange(new SqlParameter[]
    {
        new SqlParameter("@amountToDebit",amountToDebit),
        new SqlParameter("@accountId",accountId)
    });

    try
    {
        affectedRows= command.ExecuteNonQuery();
    }
    catch (Exception ex)
    {
        throw ex;
    }
    return affectedRows == 1;
}

该函数需要四个参数,来自调用者的数据库连接对象和事务对象,以及扣款账户id和扣款金额,知道了这些参数之后就可以执行扣款操作的更新查询。

现在,假设这个函数以类库的行为提供给我们,很显然,我们什么都不能更改。这种情况,我们不能使用Database.BeginTransaction方法,因为我们需要将SqlConnectionSqlTransaction对象传给该函数,并把该函数放到我们的事务里。这样,我们就需要首先创建一个SqlConnection,然后开始SqlTransaction。代码如下:

    #region 7.0 使用已存在的事务
    int outputId = 2, inputId = 1;
    decimal transferAmount = 1000m;
    var connectionString = ConfigurationManager.ConnectionStrings["ConcurrencyAndTransactionManagementConn"].ConnectionString;
    using (var conn=new SqlConnection(connectionString))
    {
        conn.Open();
        using (var trans=conn.BeginTransaction())
        {
            try
            {
                var result = DebitOutputAccount(conn, trans, outputId, transferAmount);
                if (!result)
                {
                    throw new Exception("不能正常扣款!");
                }
                using (var db=new Context(conn,contextOwnsConnection:false))
                {
                    db.Database.UseTransaction(trans);
                    var inputAccount=db.InputAccounts.Find(inputId);
                    inputAccount.Balance += transferAmount;
                    db.SaveChanges();
                }
                trans.Commit();
            }
            catch (Exception ex)
            {
                trans.Rollback();
            }
        }
    }

    #endregion

稍作解释,首先创建了一个SqlConnection,然后使用该连接关联了一个SqlTransaction。事务开始后,我们就使用连接和事务对象调用老项目中的方法,然后检查了一下调用老项目中的方法是否执行成功!如果失败,我们直接抛出异常,捕获异常后会回滚该事务。如果成功了,我们使用了DbContext类来为InputAccounts用户添加收款,并提交事务。这里有一句代码值得注意db.Database.UseTransaction(trans);,这句话的意思是,EF执行的操作都在外部传入的事务中执行。还有,contextOwnsConnection的值为false,表示上下文和数据库连接没有关系,上下文释放了,数据库连接还没释放;反之为true的话,上下文释放了,数据库连接也就释放了。

选择合适的事务管理

目前,我们已经知道了好几种使用EF处理事务的方法,下面一一对号入座:

  • 如果只有一个DbContext类,那么应该尽力使用EF的默认事务管理。我们总应该将所有的操作组成一个在相同的DbContext对象的作用域中执行的工作单元,SaveChanges()方法会处理提交事务。
  • 如果使用了多个DbContext对象,那么管理事务的最佳方法可能就是把调用放到TransactionScope对象的作用域中了。
  • 如果要执行原生SQL,并想把这些操作和事务关联起来,那么应该使用EF提供的Database.BeginTransaction()方法。然而这种方法只支持EF6,不支持之前的版本。
  • 如果想为要求SqlTransaction的老项目使用EF,那么可以使用Database.UseTransaction()方法,在EF6中可用。

本章小结

首先,我们看了下如何管理EF中并发相关的问题,然后讨论了如何使用EF实现积极并发。我们也看了消极并发的一些基本概念以及为什么EF不支持和不推荐使用消极并发。

然后,我们看了如何使用EF管理事务。先是看了EF管理事务的默认实现,然后看了使用EF控制事务管理,最后看到了使用EF实现应用程序需要的大多数信息。

自我测试

  1. EntityTypeConfiguration类中,需要调用什么方法将一个属性标记为并发属性?
  2. 哪一种异常类型表示并发错误?

如果您觉得这篇文章对您有价值或者有所收获,请点击右下方的店长推荐,然后查看答案,谢谢!
查看答案



参考书籍:
《Mastering Entity Framework》
《Code-First Development with Entity Framework》
《Programming Entity Framework Code First》

时间: 2024-11-05 11:55:46

Code First开发系列之管理并发和事务(转)的相关文章

8天掌握EF的Code First开发系列之3 管理数据库创建,填充种子数据以及LINQ操作详解

本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LINQ to Entities 使用LINQ to Entities操作实体 LINQ操作 懒加载和预加载 插入数据 更新数据 删除数据 本章小结 本人的实验环境是VS 2013 Update 5,windows 10,MSSQL Server 2008. 上一篇<Code First开发系列之领域建模和管理实体关系>,我们主要介绍了EF中“约定大于配置”的概念,如何创建数据

8天掌握EF的Code First开发系列之2 简单的CRUD操作

本文出自8天掌握EF的Code First开发系列,经过自己的实践整理出来. 本篇目录 创建控制台项目 根据.Net中的类来创建数据库 简单的CRUD操作 数据库模式更改介绍 本章小结 本人的实验环境是VS 2012,windows 7,MSSQL Server 2008 R2. 创建控制台项目 1. 新建控制台应用项目 2. 通过NuGet安装Entity Framework 6 根据.Net中的类来创建数据库 上面的步骤之后,我们就可以开始写代码了.在写代码之前,你要始终记得,每个类就是相应

[ PHP 内核与扩展开发系列] 内存管理 —— 引用计数

对于 PHP 这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错.另一方面,除了要安全申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免.我们来看下面这段 PHP 代码: <?php $a = 'Hello World'; $b = $a; unset($a); 第一条语句执行后,PHP 创建了 $a 这个变量,并为它申请了

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序处理并发

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第十篇:为ASP.NET MVC应用程序处理并发 原文:Handling Concurrency with the Entity Framework 6 in an ASP.NET MVC 5 Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中,您已经学习了如何更新数据.在本节教

ABP开发框架前后端开发系列---(11)菜单的动态管理

在前面随笔<ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理>中介绍了基于ABP框架服务构建的Winform客户端,客户端通过Web API调用的方式进行获取数据,从而实现了对组织机构.角色.用户.权限等管理,其中没有涉及菜单部分,本篇随笔介绍在ABP框架中实现菜单的管理,菜单是作为Winform或者Web动态构建界面的一个重要元素,同时也是作为角色权限控制的部分资源. 1.菜单的列表展示和管理 一般情况下,菜单的树形列表的显示可以分为多个节点,节点可以收缩也可以展开,当然节点

ABP开发框架前后端开发系列---(12)配置模块的管理

一般来说,一个系统或多或少都会涉及到一些系统参数或者用户信息的配置,而ABP框架也提供了一套配置信息的管理模块,ABP框架的配置信息,必须提前定义好配置的各项内容,然后才能在系统中初始化或者通过接口查询来使用,本篇随笔引入了另外一种配置信息的定义,实现更加简化的处理,本篇随笔着重介绍两者之间的差异和不同的地方. 1.ABP框架的配置管理 如下面是邮件配置信息,配置信息一般先继承自SettingProvider,初始化定义后,才能被系统所使用. EmailSettingProvider:继承自Se

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序读取相关数据

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第六篇:为ASP.NET MVC应用程序读取相关数据 原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中您已经完成了学校数据模型.在本教程中你将

S3C2416裸机开发系列十七_GCC下Fatfs的移植

S3C2416裸机开发系列十七 GCC下Fatfs的移植 象棋小子    1048272975 对于固态存储器,其存储容量可以很大,往往需要一款文件系统对存储器用户数据进行组织文件的管理.它对文件存储器空间进行组织和分配,负责文件的存储并对存入的文件进行保护和检索.在嵌入式系统中,往往需要采用windows兼容的文件系统,像相机的照片.视频监控.语音产品等,很多都需要从windows计算机上提取资源或在windows计算机上进一步处理.Fatfs由于其开源免费,支持fat32,受到了广泛的应用,

iOS开发系列--数据存取

概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列—Objective-C之Foundation框架的文章中提到归档.plist文件存储,包括偏好设置其本质都是存储为文件,只是说归档或者plist文件存储可以选择保存到沙盒中,而偏好设置系统已经规定只能保存到沙盒的Library/Preferences目录.当然,文件存储并不作为本文的重点内容.本文重点还是说数据库存储,做过数据库开发的朋友应该知道,可以通过SQL直接访问数据库,也可以