Net6/NetCore3.1搭建codeFirst 【支持多dbcontext】并接合TransactionScope 完成事务操作

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

1、打开VS2019或VS2022创建一个webApi项目

添加引用

1.Microsoft.EntityFrameworkCore.SqlServer
2.Microsoft.EntityFrameworkCore.Design

本篇采用VS2019做演示

1.1、配置文件增加数据库链接字符串

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "swapDbContext": "Data Source=LAPTOP-84R6S0FB;Initial Catalog=swap;Integrated Security=True;MultipleActiveResultSets=true"
  }
}

1.2、Startup.cs中ConfigureServices引入Sqlserver使用

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<swapDbContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("swapDbContext")), ServiceLifetime.Scoped);
        }

2、为解决方案增加一个类库,命名为CoreDbContext

添加引用

1.Microsoft.EntityFrameworkCore.Relational
2.Microsoft.EntityFrameworkCore.Tools
3.Microsoft.EntityFrameworkCore.SqlServer

2.1、新建DbConfigs、DbDtos、文件夹及swapDbContext.cs类

 

 2.2、在DbDtos文件夹创建一个简单的实体

namespace CoreDbContext.DbDtos
{
    public class book
    {
        public string uid { get; set; }
        public string bookName { get; set; }
        public bool IsDeleted { get; set; }
    }
}

2.3、在DbConfigs文件夹中创建实体配置【指定主键,全局筛选器,字段长度、字段备注等】

    internal class bookConfig : IEntityTypeConfiguration<book>
    {
        public void Configure(EntityTypeBuilder<book> builder)
        {
            builder.ToTable("T_books");
            builder.HasKey(A => A.uid);
            builder.HasQueryFilter(A => A.IsDeleted == false);
            builder.Property(A => A.bookName).HasMaxLength(50).HasComment("书名");
        }
    }

2.4、创建数据库上下文

namespace CoreDbContext
{
    /// <summary>
    /// add-Migration -Context  可以通过-Context 指定上下文,因此,项目中可以有多个DbCOntext,这样就支持多个数据库
    /// </summary>
    public class swapDbContext : DbContext
    {
        public DbSet<book> books { get; set; }

        public swapDbContext(DbContextOptions<swapDbContext> options) : base(options)
        {
        }


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //从当前程序集命名空间加载所有的IEntityTypeConfiguration
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
    }

    /// <summary>
    /// 开发环境专用  用于add-Migration 时使用
    /// </summary>
    public class swapDbContextFactory : IDesignTimeDbContextFactory<swapDbContext>
    {
        public swapDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<swapDbContext>();
            optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=swap;Integrated Security=True;MultipleActiveResultSets=true");

            return new swapDbContext(optionsBuilder.Options);
        }
    }
}

3、迁移数据库

Add-Migration initDb

Update-DataBase

 

 4、查看生成的数据库及表

 

 5、接合TransactionScope进行简单事务测试

关于TransactonScope可参考:https://www.cnblogs.com/csdbfans/p/TransactionScope.html

5.1、验证方法题内只有一个SaveChanges()时,执行的是事务

        [HttpGet]
        public IActionResult TestBook()
        {
            book book = new book()
            {
                uid = Guid.NewGuid().ToString(),
                bookName = "平凡的世界",
                IsDeleted = false
            };
            dbContext.books.Add(book);
            string name = "";
            for (int i = 0; i < 20; i++)
            {
                name += "平凡的世界";
            }
            book book2 = new book()
            {
                uid = Guid.NewGuid().ToString(),
                bookName = name,
                IsDeleted = false
            };
            dbContext.books.Add(book2);
            dbContext.SaveChanges();
            return Ok();
        }
View Code

上述方法内只有一个SaveChanges,但书的名称超出了最大值50,是否会引起事务回滚?

调试如下:

 

 数据库插入失败

 

 验证结果:方法内只有一个SaveChanges时,EFCORE执行的是事务操作,因此当方法内只有一个SaveChanges时,就无须再使用TransactionScope了

5.2、验证方法题内有多个SaveChanges()时,执行的不是事务

将上述方法增加一个saveChagnes

 

 

 调试后,数据库会增加一条记录

 

  验证结果:方法内有多个SaveChanges时,EFCORE执行的是不事务操作,此时就需要接合使用TransactionScope了

5.3、接合TransactionScope来使用

        [HttpGet]
        public IActionResult TestBook()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                book book = new book()
                {
                    uid = Guid.NewGuid().ToString(),
                    bookName = "平凡的世界",
                    IsDeleted = false
                };
                dbContext.books.Add(book); 
                dbContext.SaveChanges();

                book book1 = new book()
                {
                    uid = Guid.NewGuid().ToString(),
                    bookName = "钢铁是怎样炼成的",
                    IsDeleted = false
                };
                dbContext.books.Add(book1);
                dbContext.SaveChanges();

                string name = "";
                for (int i = 0; i < 20; i++)
                {
                    name += "平凡的世界";
                }
                FormattableString sql = $"insert into [T_books] values(newid(),{name},0)";
                dbContext.Database.ExecuteSqlInterpolated(sql); 
                scope.Complete();
                return Ok();
            }
            
        }
View Code

注意,dbContext.Database.ExecuteSqlInterpolated()中的参数是带有内插值的字符串,字符串中无需包含单引号

调试如下:

 

 数据库如下【之前插入的一条,我已删除】:

 

 验证结果:在多个saveChanges时或者和原生SQL语句接合使用时,使用TransactionScope可以保证执行的是事务

 5.4、异步操作的TransactionScope声明【须使用参数:TransactionScopeAsyncFlowOption.Enabled】

        [HttpGet]
        public async Task<IActionResult> TestBook()
        {
            using (TransactionScope scope = new TransactionScope( TransactionScopeAsyncFlowOption.Enabled))
            {
                book book = new book()
                {
                    uid = Guid.NewGuid().ToString(),
                    bookName = "平凡的世界",
                    IsDeleted = false
                };
                dbContext.books.Add(book); 
               await dbContext.SaveChangesAsync();

                book book1 = new book()
                {
                    uid = Guid.NewGuid().ToString(),
                    bookName = "钢铁是怎样炼成的",
                    IsDeleted = false
                };
                dbContext.books.Add(book1);
                await dbContext.SaveChangesAsync();
                 
                scope.Complete();
                return Ok();
            }
            
        }
View Code

6、多个DbContext上下文时怎么做数据库迁移

6.1、项目中新增TestDbContext

namespace CoreDbContext
{ 

    /// <summary>
    /// add-Migration -Context  可以通过-Context 指定上下文,因此,项目中可以有多个DbCOntext,这样就支持多个数据库
    /// </summary>
    public class TestDbContext : DbContext
    {
        public DbSet<book> books { get; set; }

        public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
        {
        }


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //从当前程序集命名空间加载所有的IEntityTypeConfiguration
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
    }

    /// <summary>
    /// 开发环境专用  用于add-Migration 时使用
    /// </summary>
    public class TestDbContextFactory : IDesignTimeDbContextFactory<TestDbContext>
    {
        public TestDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
            optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=TestDb;Integrated Security=True;MultipleActiveResultSets=true");

            return new TestDbContext(optionsBuilder.Options);
        }
    }
}
View Code

6.2、配置文件中新增链接字符串并在Startup.cs中注册TestDbContext

 

 

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<swapDbContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("swapDbContext")), ServiceLifetime.Scoped);
            services.AddDbContext<TestDbContext>(options =>
              options.UseSqlServer(Configuration.GetConnectionString("TestDbContext")), ServiceLifetime.Scoped);
        }

6.3、数据迁移

 

 数据迁移时报错,要求带上  -Context 参数

因此,当项目中有多个DbContext时,我们在做数据库迁移时需要指定context

Add-Migration addTestDb -context TestDbContext

Update-DataBase -context  TestDbContext

 

 6.4、迁移成功,查看数据库

 

 多个数据库上下文就使得项目支持多数据库链接,使用场景如连接备份数据库、配置数据库等

@天才卧龙的博科人

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章