返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >EntityFramework使用CodeFirst模式管理事务
  • 861
分享到

EntityFramework使用CodeFirst模式管理事务

2024-04-02 19:04:59 861人浏览 八月长安
摘要

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

一、什么是事务

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

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

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

1、开始事务。

2、执行所有的查询,执行所有的数据库操作,这些操作被视为一个工作单元。

3、如果所有的事务成功了,那么提交事务。

4、如果任何一个操作失败,就回滚事务。

二、创建测试环境

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

OutputAccount实体类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

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

    }
}

InputAccount实体类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

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

    }
}

2、定义数据上下文类

using EFTransactionApp.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.EF
{
    public class EFDbContext:DbContext
    {
        public EFDbContext()
            : base("name=AppConnection")
        {

        }


        public DbSet<OutputAccount> OutputAccounts { get; set; }

        public DbSet<InputAccount> InputAccounts { get; set; }
    }
}

3、使用数据迁移生成数据库,并填充种子数据

namespace EFTransactionApp.Migrations
{
    using EFTransactionApp.Model;
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<EFTransactionApp.EF.EFDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(EFTransactionApp.EF.EFDbContext context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.

            // 填充种子数据
            context.InputAccounts.AddOrUpdate(
                  new InputAccount()
                  {
                      Name = "李四",
                      Balance = 0M
                  }
                );

            context.OutputAccounts.AddOrUpdate(
                 new OutputAccount()
                 {
                     Name="张三",
                     Balance=10000M
                 }
                );
        }
    }
}

4、运行程序

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

运行程序前,先查看数据库数据:

现在,我们尝试使用EF的事务从OutputAccount的张三转入1000给InputAccount的李四。

使用EF默认的事务执行

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

using EFTransactionApp.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new EFDbContext())
            {
                int outputId = 1, inputId = 1;
                decimal transferAmount = 1000m;
                //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();
            }

        }
    }
}

运行程序后,会发现数据库中数据发生了改变:

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

可能有人会疑惑:上面的程序执行成功了,没有看到事务的效果,能不能修改一下代码让上面的程序执行失败然后可以看到事务的效果呢?答案是肯定可以的,下面将上面的代码进行修改。

通过查看数据库表结构会发现Balance的数据类型是

意味着Balance列的最大可输入长度是16位(最大长度18位减去2位小数点),如果输入的长度大于16位的话程序就会报错,所以将上面的代码进行如下的修改:

using EFTransactionApp.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new EFDbContext())
            {
                int outputId = 1, inputId = 1;
                decimal transferAmount = 1000m;
                //1 检索事务中涉及的账户
                var outputAccount = db.OutputAccounts.Find(outputId);
                var inputAccount = db.InputAccounts.Find(inputId);
                //2 从输出账户上扣除1000
                outputAccount.Balance -= transferAmount;
                //3 从输入账户上增加1000 *3000000000000000倍
                inputAccount.Balance += transferAmount*3000000000000000;
                //4 提交事务
                db.SaveChanges();
            }

        }
    }
}

在次运行程序,会发现程序报错了:

这时在查看数据库,发现用户张三的余额还是9000没有发生变化,说明事务起作用了。

5、使用TransactionScope处理事务

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

int outputId = 1, inputId = 1;
decimal transferAmount = 1000m;
using (var ts = new TransactionScope(TransactionScopeOption.Required))
{
       var db1 = new EFDbContext();
       var db2 = new EFDbContext();
       //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();
}

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

三、使用EF6管理事务

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

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

int outputId = 1, inputId = 1; decimal transferAmount = 1000m;
using (var db = new EFDbContext())
{
                using (var trans = db.Database.BeginTransaction())
                {
                    try
                    {
                        var sql = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@outputId";
                        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();
                    }
                }
}

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

四、使用已经存在的事务

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

1、一些操作可能在应用的不同部分完成。

2、对老项目使用了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 Balance=Balance-@amountToDebit where id=@accountId";
            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;
}

 这种情况,我们不能使用Database.BeginTransaction()方法,因为我们需要将SqlConnection对象和SqlTransaction对象传给该函数,并把该函数放到我们的事务里。这样,我们就需要首先创建一个SqlConnection,然后开始SqlTransaction,代码如下:

int outputId = 2, inputId = 1; decimal transferAmount = 1000m;
var connectionString = ConfigurationManager.ConnectionStrings["AppConnection"].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 EFDbContext(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();
                    }
                }
}

同时,需要修改数据上下文类,数据库上下文类代码修改如下:

using EFTransactionApp.Model;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFTransactionApp.EF
{
    //contextOwnsConnection
    //false:表示上下文和数据库连接没有关系,上下文释放了,数据库连接还没释放;
    //true:上下文释放了,数据库连接也就释放了。
    public class EFDbContext:DbContext
    {
        //public EFDbContext()
        //    : base("name=AppConnection")
        //{

        //}

        public EFDbContext(DbConnection conn, bool contextOwnsConnection)
            : base(conn, contextOwnsConnection)
        {

        }


        public DbSet<OutputAccount> OutputAccounts { get; set; }

        public DbSet<InputAccount> InputAccounts { get; set; }
    }
}

五、选择合适的事务管理

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

1、如果只有一个DbContext类,那么应该尽力使用EF的默认事务管理。我们总应该将所有的操作组成一个在相同的DbContext对象的作用域中执行的工作单元,SaveChanges()方法会提交处理事务。

2、如果使用了多个DbContext对象,那么管理事务的最佳方法可能就是把调用放到TransactionScope对象的作用域中了。

3、如果要执行原生的SQL命令,并想把这些操作和事务关联起来,那么应该使用EF提供的Database.BeginTransaction()方法。然而这种方法只支持EF6以后的版本,以前的版本不支持。

4、如果想为要求SqlTransaction的老项目使用EF,那么可以使用Database.UseTransaction()方法,在EF6中可用。

示例代码下载地址:点此下载

到此这篇关于Entity Framework使用Code First模式管理事务的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程网。

--结束END--

本文标题: EntityFramework使用CodeFirst模式管理事务

本文链接: https://lsjlt.com/news/141424.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • EntityFramework使用CodeFirst模式管理事务
    一、什么是事务 处理以数据为中心的应用时,另一个重要的话题是事务管理。ADO.NET为事务管理提供了一个非常干净和有效的API。因为EF运行在ADO.NET之上,所以EF可以使用AD...
    99+
    2024-04-02
  • EntityFramework使用CodeFirst模式管理视图
    一、什么是视图 视图在RDBMS(关系型数据库管理系统)中扮演了一个重要的角色,它是将多个表的数据联结成一种看起来像是一张表的结构,但是没有提供持久化。因此,可以将视图看成是一个原生...
    99+
    2024-04-02
  • EntityFramework使用CodeFirst模式管理数据库
    一、管理数据库连接 1、使用配置文件管理连接之约定 在数据库上下文类中,如果我们只继承了无参数的DbContext,并且在配置文件中创建了和数据库上下文类同名的连接字符串,那么EF会...
    99+
    2024-04-02
  • EntityFramework使用CodeFirst模式管理存储过程
    在EF中使用存储过程和使用视图是很相似的,一般会使用Database对象上的两个方法:SqlQuery和ExecuteSqlCommand。为了从存储过程中读取很多数据行,我们只需要...
    99+
    2024-04-02
  • Entity Framework怎么使用Code First模式管理事务
    今天小编给大家分享一下Entity Framework怎么使用Code First模式管理事务的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后...
    99+
    2023-06-29
  • EF使用CodeFirst模式生成单数形式表名
    使用Code-First模式生成数据库时,默认生成的数据库表的名称为类型的复数形式,例如实体类名称是"User",默认生成的数据库表名为“Users&...
    99+
    2024-04-02
  • 分布式事务使用Seata的AT事务模式如何理解
    分布式事务使用Seata的AT事务模式如何理解,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。项目使用了微服务,并且将一些模块进行了拆分,现在遇到了一个批量保存的场景,而且还...
    99+
    2023-06-19
  • EF使用CodeFirst模式给实体类添加复合主键
    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations...
    99+
    2024-04-02
  • vue使用Vuex状态管理模式
    目录1、基于单向数据流问题而产生了Vuex2、安装及使用3、核心及使用方法(1)State(2) Getters(3) Mutation(4) Action(5)Modules4、V...
    99+
    2024-04-02
  • 如何在Spring中使用声明式事务管理
    本篇文章为大家展示了如何在Spring中使用声明式事务管理,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。基于TransactionProxyFactoryBean的事务管理配置Spring中配置AO...
    99+
    2023-05-31
    spring 声明式事务管理
  • .Net5使用EFCore CodeFirst模式 数据迁移并实现DBContext依赖注入
    原文:https://www.cnblogs.com/yuzhouk/p/14638474.html...
    99+
    2017-10-13
    .Net5使用EFCore CodeFirst模式 数据迁移并实现DBContext依赖注入 数据库入门 数据库基础教程
  • Spring 事务管理详解及使用
    ✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识...
    99+
    2023-09-02
    spring java mybatis
  • vue怎么使用Vuex状态管理模式
    这篇“vue怎么使用Vuex状态管理模式”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue怎么使用Vuex状态管理模式”文...
    99+
    2023-06-29
  • Spring事务管理怎么正确使用
    这篇文章主要介绍“Spring事务管理怎么正确使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring事务管理怎么正确使用”文章能帮助大家解决问题。事务(Transaction)是访问数据库的...
    99+
    2023-07-05
  • java事务管理的方式有哪些
    Java事务管理的方式有以下几种:1. 编程式事务管理:在代码中手动编写事务管理的代码。可以使用Java的事务管理API(如JTA)...
    99+
    2023-09-07
    java
  • springboot配置mybatis和事务管理方式
    目录一、spring boot与mybatis的配置二、事务的配置总结一、spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部...
    99+
    2023-05-15
    springboot配置mybatis springboot事务管理 事务管理mybatis
  • 怎么在Django中使用transaction管理事务
    本篇文章给大家分享的是有关怎么在Django中使用transaction管理事务,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1. transaction事务内不执行数据库的c...
    99+
    2023-06-14
  • 如何在java中使用Connection管理事务
    本篇文章给大家分享的是有关如何在java中使用Connection管理事务,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java的优点是什么1. 简单,只需理解基本的概念,就可...
    99+
    2023-06-14
  • Spring事务管理的使用细则浅析
    目录Spring 事务管理接口1、事务管理器接口 PlatformTransactionManager2、事务定义接口 TransactionDefinitionSpring 事务管...
    99+
    2023-02-27
    Spring事务管理机制 Spring事务管理方式
  • 如何利用Redis实现分布式事务管理
    如何利用Redis实现分布式事务管理引言:随着互联网的快速发展,分布式系统的使用越来越广泛。在分布式系统中,事务管理是一项重要的挑战。传统的事务管理方式在分布式系统中难以实现,并且效率低下。而利用Redis的特性,我们可以轻松地实现分布式事...
    99+
    2023-11-07
    管理 redis 分布式事务
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作