.Net Core 教程 Part4 – (36)(37)多层项目中EF的使用

Part4 – (36) 与配置系统的集成

分层项目的建立

1、为什么要项目分层?带来什么问题?

2、创建一个.NET类库项目BooksEFCore,放实体等类。NuGet:Microsoft.EntityFrameworkCore.Relational

3、 BooksEFCore中增加实体类Book和配置类

Step1: 在创建的类库中增加实体类Book, 代码如下:

namespace Part4_36_EFCoreBooks
{
    public class Book
    {
        public long Id { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
        public double Price { get; set; }   
        public DateTime PubDate { get; set; }   
    }
}

Step2: 接着创建Book实体的配置类,修改了表名,代码如下:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Part4_36_EFCoreBooks
{
    internal class BookConfig : IEntityTypeConfiguration<Book>
    {
        public void Configure(EntityTypeBuilder<Book> builder)
        {
            builder.ToTable("T_Books");
        }
    }
}

Step3: 然后创建DbContext数据库上下文,代码如下:

using Microsoft.EntityFrameworkCore;

namespace Part4_36_EFCoreBooks
{
    public class MyDbContext: DbContext
    {
        public DbSet<Book> Books { get; set; }
        //参数直接传给父类
        public MyDbContext(DbContextOptions<MyDbContext> options):base(options)
        { 
        
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            //optionsBuilder.UseDSqlServer(.....); 
            //直接在这里写连接字符串 DbContext就直接被写死了 只能连接一种数据库
            //想要灵活 用的时候再决定用什么数据库 什么连接字符串 最好推迟在
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //这么写的话 直接就是从web那个项目查找相关配置类 找不到在EFCore项目中的配置了
            //modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetEntryAssembly()); 
            //从哪一个程序集来加载实体的配置类 从当前DbContext所在的程序集(EFCoreBook)来加载
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
    }
}

数据库的配置

1、上下文类MyDbContext :为什么正式项目中最好不要在MyDbContext写数据库配置(连接不同的DB甚至不同类型的DB)。尽量数据库配置的代码写到ASP.NET Core项目中。不重写OnConfiguring方法,而是为MyDbContext类的构造方法增加DbContextOptions<MyDbContext>参数。在ASP.NET Core项目对DbContextOptions的配置。

2、创建ASP.NET Core项目,添加对“BooksEFCore”项目的引用。NuGet安装Microsoft.EntityFrameworkCore.SqlServer

3、配置文件、配置代码等放到ASP.NET Core项目中。

//注入了DbContext 之后使用就不需要new了
builder.Services.AddDbContext<MyDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr);
});

4、在Controller中注入MyDbContext,编写测试代码。

using Microsoft.AspNetCore.Mvc;
using Part4_36_EFCoreBooks;

namespace Part4_36_WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private readonly MyDbContext dbCtx;

        public TestController(MyDbContext dbCtx)
        {
            this.dbCtx = dbCtx;
        }
        [HttpGet]
        public string Demo1()
        {
            int c=dbCtx.Books.Count();
            return $"c={c}";
        }
    }
}

数据库迁移

5、生成实体类的迁移脚本。多项目的环境下执行Add-Migration的时候可能会出现这个错误,原理是什么?

.Net Core 教程 Part4 – (36)(37)多层项目中EF的使用
迁移数据库出错

6、不用研究多项目中Add-Migration的细节。实用的方案:编写实现IDesignTimeDbContextFactory接口的类,把配置放到里面,反正是开发环境用而已。

7、可以把连接字符串配置到环境变量中,不过MyDesignTimeDbContextFactory中来读取配置系统,可以直接用Environment.GetEnvironmentVariable() 读取环境变量。

8、数据库迁移脚本要生成到BooksEFCore中,因此为这个项目安装Microsoft.EntityFrameworkCore.ToolsMicrosoft.EntityFrameworkCore.SqlServer。然后把BooksEFCore设置为启动项目,并且在【程序包管理器控制台】中也选中BooksEFCore项目后,执行Add-MigrationUpdate-Database

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace Part4_36_EFCoreBooks
{
    //这个类里面很难读取配置系统 给Migration工具用的 只是开发环境用的 用于Add-Migration Update-Database等操作,正式环境不会用它
    internal class MyDbContextDesignFac : IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
            //返回一个合适的DbCOntext
            DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
            string connStr = Environment.GetEnvironmentVariable("ConnStr");
            builder.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;");
            MyDbContext ctx = new MyDbContext(builder.Options);
            return ctx;
        }
    }
}

步骤汇总

1、建类库项目,放实体类、DbContext、配置类等

DbContext中不配置数据库连接,而是为DbContext增加一个DbContextOptions类型的构造函数。

2、EFCore项目安装对应数据库的EFCore Provider

3、asp.net core项目引用EFCore项目,并且通过AddDbContext来注入DbContext及对DbContext进行配置。

4、Controller中就可以注入DbContext类使用了。

5、让开发环境的Add-Migration知道连接哪个数据库

在EFCore项目中创建一个实现了IDesignTimeDbContextFactory的类。

并且在CreateDbContext返回一个连接开发数据库的DbContext。

public MyDbContext CreateDbContext(string[] args)
{
    DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
    string connStr = "Data Source=.;Initial Catalog=demo666;Integrated Security=SSPI;";
    builder.UseSqlServer(connStr);
    MyDbContext ctx = new MyDbContext(builder.Options);
    return ctx;
}

如果不在乎连接字符串被上传到Git,就可以把连接字符串直接写死到CreateDbContext;如果在乎,那么CreateDbContext里面很难读取到VS中通过简单的方法设置的环境变量,所以必须把连接字符串配置到Windows的正式的环境变量中,然后再 Environment.GetEnvironmentVariable读取。

6、正常执行Add-Migration、Update-Database迁移就行了。需要把EFCore项目设置为启动项目,并且在【程序包管理器控制台】中也要选中EFCore项目,并且安装Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools

慎用AddDbContextPool

1、用AddDbContextPool代替AddDbContext可以实现“DbContext池”。

2、 AddDbContextPool的问题:

  • 用AddDbContextPool注册的DbContext无法注入其他服务?而AddDbContext可以。为啥要为DbContext注册服务?为什么AddDbContextPool不行?
  • 很多数据库的ADO.NET提供者都实现了数据库连接池机制,可能会有冲突,实用的时候需要自己调节。

3、AddDbContextPool意义不大: 1)“小上下文”策略 2)有数据库连接池

.Net Core 教程 Part4 – (36)(37)多层项目中EF的使用

Part4 – (37) 多个数据库上下文注入

案例:复杂.NET Core项目中批量注册上下文

1、项目采用“小上下文”策略,在项目中可能存在着多个上下文类,如果手动AddDbContext就太麻烦。

2、反射扫描程序集中所有的上下文类,然后逐个调用AddDbContext注册。Install-Package Zack.Infrastructure

用法:

//指定程序集进行扫描
var asms = new Assembly[] {Assembly.Load("EFBooks")};
//也可以加载所有程序集
var asmsAll = ReflectionHelper.GetAllReferencedAssemblies();
builder.Services.AddAllDbContext<MyDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr);
},asms );

AddAllDbContext的实现原理:

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Zack.Commons;

namespace Microsoft.EntityFrameworkCore
{
    public static class EFCoreInitializerHelper
    {
        /// <summary>
        /// 自动为所有的DbContext注册连接配置
        /// </summary>
        /// <param name="services"></param>
        /// <param name="builder"></param>
        /// <param name="assemblies"></param>
        public static IServiceCollection AddAllDbContexts(this IServiceCollection services, Action<DbContextOptionsBuilder> builder,
            IEnumerable<Assembly> assemblies)
        {
            //AddDbContextPool不支持DbContext注入其他对象,而且使用不当有内存暴涨的问题,因此不用AddDbContextPool
            Type[] types = new Type[] { typeof(IServiceCollection), typeof(Action<DbContextOptionsBuilder>), typeof(ServiceLifetime), typeof(ServiceLifetime) };
            var methodAddDbContext = typeof(EntityFrameworkServiceCollectionExtensions)
                .GetMethod(nameof(EntityFrameworkServiceCollectionExtensions.AddDbContext), 1, types);
            foreach (var asmToLoad in assemblies)
            {
                //Register DbContext
                //GetTypes() include public/protected ones
                //GetExportedTypes only include public ones
                //so that XXDbContext in Agrregation can be internal to keep insulated
                foreach (var dbCtxType in asmToLoad.GetTypes()
                    .Where(t => !t.IsAbstract && typeof(DbContext).IsAssignableFrom(t)))
                {
                    //similar to serviceCollection.AddDbContextPool<ECDictDbContext>(opt=>new DbContextOptionsBuilder(dbCtxOpt));
                    var methodGenericAddDbContext = methodAddDbContext.MakeGenericMethod(dbCtxType);
                    methodGenericAddDbContext.Invoke(null, new object[] { services, builder, ServiceLifetime.Scoped, ServiceLifetime.Scoped });
                }
            }
            return services;
        }

    }
}

本文版权归Chaoqiang's Blog站点所有,发布者:chaoqiang,转转请注明出处:http://www.zhengchaoqiang.com/1522.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-01-07 20:56
下一篇 2022-01-12 07:34

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

近期个人博客正在迁移中,原博客请移步此处,抱歉!