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的时候可能会出现这个错误,原理是什么?
6、不用研究多项目中Add-Migration的细节。实用的方案:编写实现IDesignTimeDbContextFactory接口的类,把配置放到里面,反正是开发环境用而已。
7、可以把连接字符串配置到环境变量中,不过MyDesignTimeDbContextFactory中来读取配置系统,可以直接用Environment.GetEnvironmentVariable() 读取环境变量。
8、数据库迁移脚本要生成到BooksEFCore中,因此为这个项目安装Microsoft.EntityFrameworkCore.Tools、Microsoft.EntityFrameworkCore.SqlServer。然后把BooksEFCore设置为启动项目,并且在【程序包管理器控制台】中也选中BooksEFCore项目后,执行Add-Migration和Update-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)有数据库连接池
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,转转请注明出处:https://www.zhengchaoqiang.com/1522.html