.net如何优雅的使用EFCore
EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作。功能非常强大,在老版本的时候叫做EF,后来.net core问世,EFCore也随之问世。
本文我们将用一个控制台项目Host一个web服务,并且使用本地Mysql作为数据库,使用EFCore的Code First模式进行数据操作。
DBSet清除计划
以前使用EF/EFCore的开发者应该都记得,需要在DBContext里写好多DBSet,一个表对应一个DBSet,然后在其他地方操作这些DBSet对相关的表进行增删改查。作为一个开发,这些重复操作都是我们希望避免的,我们可以利用反射机制将这些类型通过框架自带的方法循环注册进去。
1.EF实体继承统一的接口,方便我们反射获取所有EF实体,接口可以设置一个泛型,来泛化我们的主键类型,因为可能存在不同的表的主键类型也不一样。
统一的EF实体接口
public interface IEFEntity<TKey>
{
    public TKey Id { get; set; }
}
统一的接口实现类
public abstract class AggregateRoot<TKey> : IEFEntity<TKey>
{
    public TKey Id { get; set; }
}
用户实体类
public class User : AggregateRoot<string>
{
    public string UserName { get; set; }
    public DateTime Birthday { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}
2.利用反射获取某个程序集下所有的实体类
public class EFEntityInfo
{
    public (Assembly Assembly, IEnumerable<Type> Types) EFEntitiesInfo => (GetType().Assembly, GetEntityTypes(GetType().Assembly));
    private IEnumerable<Type> GetEntityTypes(Assembly assembly)
    {
        //获取当前程序集下所有的实现了IEFEntity的实体类
        var efEntities = assembly.GetTypes().Where(m => m.FullName != null
                                                        && Array.Exists(m.GetInterfaces(), t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEFEntity<>))
                                                        && !m.IsAbstract && !m.IsInterface).ToArray();
        return efEntities;
    }
}
3.DBContext实现类中OnModelCreating方法中注册这些类型
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //循环实体类型,并且通过Entity方法注册类型
    foreach (var entityType in Types)
    {
        modelBuilder.Entity(entityType);
    }
    base.OnModelCreating(modelBuilder);
}
至此为止所有的实体类都被注册到DBContext中作为DBSets,再也不需要一个个写DBSet了,可以用过DbContext.Set<User>()获取用户的DBSet。
IEntityTypeConfiguration(表配置)
用数据库创建过表的同学都知道,在设计表的时候,可以给表添加很多配置和约束,在Code First模式中,很多同学都是在对象中通过注解的方式配置字段。如下就配置了用户名是不能为NULL和最大长度为500
[Required]
[MaxLength(500)]
public string UserName { get; set; }
也有的同学在DbContext中的OnModelCreating方法配置
modelBuilder.Entity<User>().Property(x => x.UserName).IsRequired();
这两种方法,前者入侵行太强,直接代码耦合到实体类中了,后者不够清楚,把一大堆表的配置写在一个方法里,当然了很多人说可以拆分不同的方法或者使用注释分开。但是!不够优雅!
我们可以使用IEntityTypeConfiguration接口实现我们所想的优雅的表配置。
1.创建一个配置基类,继承自IEntityTypeConfiguration,做一些通用的配置,比如设置主键,一般都是id啦,还有软删除等。
public abstract class EntityTypeConfiguration<TEntity, TKey> : IEntityTypeConfiguration<TEntity>
       where TEntity : AggregateRoot<TKey>
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        var entityType = typeof(TEntity);
        builder.HasKey(x => x.Id);
        if (typeof(ISoftDelete).IsAssignableFrom(entityType))
        {
            builder.HasQueryFilter(d => EF.Property<bool>(d, "IsDeleted") == false);
        }
    }
}
2.创建用户实体/表独有的配置,比如设置用户名的最大长度,以及seed一些数据
public class UserConfig : EntityTypeConfiguration<User, string>
{
    public override void Configure(EntityTypeBuilder<User> builder)
    {
        base.Configure(builder);
        builder.Property(x => x.UserName).HasMaxLength(50);
        //mock一条数据
        builder.HasData(new User()
        {
            Id = "090213204",
            UserName = "Bruce",
            Birthday = DateTime.Parse("1996-08-24")
        });
    }
}
当然还有很多配置可以设置,比如索引,导航属性,唯一键等。如下图书实体
public class BookConfig : EntityTypeConfiguration<Book, long>
{
    public override void Configure(EntityTypeBuilder<Book> builder)
    {
        base.Configure(builder);
        builder.Property(x => x.Id).ValueGeneratedOnAdd(); //设置book的id自增
        builder.Property(x => x.BookName).HasMaxLength(500).IsRequired();
        builder.HasIndex(x => x.Author);//作者添加索引
        builder.HasIndex(x => x.SN).IsUnique();//序列号添加唯一索引
        builder.HasOne(r => r.User).WithMany(x=>x.Books)
            .HasForeignKey(r => r.UserId).IsRequired();//导航属性,本质就是创建外键,虽然查询很方便,生产中不建议使用!!!
    }
}
3.DBContext中应用配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasCharSet("utf8mb4 ");
    var (Assembly, Types) = _efEntitysInfo.EFEntitiesInfo;
    foreach (var entityType in Types)
    {
        modelBuilder.Entity(entityType);
    }
    //只需要将配置类所在的程序集给到,它会自动加载
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly);
    base.OnModelCreating(modelBuilder);
}
Repository(仓储)
这个不过分介绍,特别是基于http的微服务中基本都有这个。
1.创建一个仓储基类,对于不同的实体,创建一样的增删改查方法。
简单写几个查询的方法定义。
public interface IAsyncRepository<TEntity, Tkey> where TEntity : class
{
    IQueryable<TEntity> All();
    IQueryable<TEntity> All(string[] propertiesToInclude);
    IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter);
    IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> filter, string[] propertiesToInclude);
}
2.创建仓储实现类,将DBContext注入到构造中
public class GenericRepository<TEntity, Tkey> : IAsyncRepository<TEntity, Tkey> where TEntity : class
{
    protected readonly LibraryDbContext _dbContext;
    public GenericRepository(LibraryDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    ~GenericRepository()
    {
        _dbContext?.Dispose();
    }
    public virtual IQueryable<TEntity> All()
    {
        return All(null);
    }
    public virtual IQueryable<TEntity> All(string[] propertiesToInclude)
    {
        var query = _dbContext.Set<TEntity>().AsNoTracking();
        if (propertiesToInclude != null)
        {
            foreach (var property in propertiesToInclude.Where(p => !string.IsNullOrWhiteSpace(p)))
            {
                query = query.Include(property);
            }
        }
        return query;
    }
}
Autofac
1.注入DBContext到Repository的构造方法中,并且注入Repository
public class EFCoreEleganceUseEFCoreModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);
        builder.RegisterModule<EFCoreEleganceUseDomainModule>(); //注入domain模块
        builder.RegisterGeneric(typeof(GenericRepository<,>))//将dbcontext注入到仓储的构造中
                .UsingConstructor(typeof(LibraryDbContext))
                .AsImplementedInterfaces()
                .InstancePerDependency();
        builder.RegisterType<WorkUnit>().As<IWorkUnit>().InstancePerDependency();
    }
}
2.Domain注入EFEntityInfo
public class EFCoreEleganceUseDomainModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<EFEntityInfo>().SingleInstance();
    }
}
数据库配置
1.注入DBContext,从配置文件读取数据库配置,然后根据开发/生产环境做一些特殊处理
var mysqlConfig = hostContext.Configuration.GetSection("Mysql").Get<MysqlOptions>();
var serverVersion = new MariaDbServerVersion(new Version(mysqlConfig.Version));
services.AddDbContextFactory<LibraryDbContext>(options =>
{
    options.UseMySql(mysqlConfig.ConnectionString, serverVersion, optionsBuilder =>
    {
        optionsBuilder.MinBatchSize(4);
        optionsBuilder.CommandTimeout(10);
        optionsBuilder.MigrationsAssembly(mysqlConfig.MigrationAssembly);//迁移文件所在的程序集
        optionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
    }).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    //开发环境可以打开日志记录和显示详细的错误
    if (hostContext.HostingEnvironment.IsDevelopment())
    {
        options.EnableSensitiveDataLogging();
        options.EnableDetailedErrors();
    }
});
项目架构和源码

项目只是一个demo架构,并不适用于生产,主程序是一个控制台项目,只需要引用相关的包和模块,就可以启动一个web host.
全部代码已经全部上传到github:https://github.com/BruceQiu1996/EFCoreDemo
该项目是一个可以启动运行的基于.net6的控制台项目,启动后会启动一个web host和一个swagger页面。
.net如何优雅的使用EFCore的更多相关文章
- 探讨EFCore如何优雅的实现读写分离
		
前言 我们都知道当单库系统遇到性能瓶颈时,读写分离是首要优化手段之一.因为绝大多数系统读的比例远高于写的比例,并且大量耗时的读操作容易引起锁表导致无发写入数据,这时读写分离就更加重要了. ...
 - 编写优雅代码,从挖掉恶心的if/else 开始
		
背景 长话短说, 作为开发人员经常需要根据条件灵活查询数据库,不管你是用rawsql 还是EFCore, 以下类似伪代码大家都可能遇到: /// <summary> /// 灵活查询 能耗 ...
 - 从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性注入
		
故事背景 最近在把自己的一个老项目从Framework迁移到.Net Core 3.0,数据访问这块选择的是EFCore+Mysql.使用EF的话不可避免要和DbContext打交道,在Core中的常 ...
 - 【限时免费】AppBoxCore - 细粒度权限管理框架(EFCore+RazorPages+async/await)!
		
目录 前言 全新AppBoxCore RazorPages 和 TagHelpers 技术架构 页面处理器和数据库操作的异步调用 Authorize特性和自定义权限验证过滤器 Authorize登录授 ...
 - [Egret]优雅的写http
		
首先,自从使用链式调用的写法后,就一发不可收拾的喜爱上了这种优雅的方式.不管是写架构还是写模块,我都会不自觉的使用这种最优雅的方式.链式写法既减少了代码量,又非常优雅的. 在使用 egret 的htt ...
 - 如何优雅地使用Sublime Text
		
Sublime Text:一款具有代码高亮.语法提示.自动完成且反应快速的编辑器软件,不仅具有华丽的界面,还支持插件扩展机制,用她来写代码,绝对是一种享受.相比于难于上手的Vim,浮肿沉重的Eclip ...
 - 阿里巴巴最新开源项目 - [HandyJSON]  在Swift中优雅地处理JSON
		
项目名称:HandyJSON 项目地址:https://github.com/alibaba/handyjson 背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发 ...
 - doT js 模板引擎【初探】要优雅不要污
		
js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. ...
 - PostCSS一种更优雅、更简单的书写CSS方式
		
Sass团队创建了Compass大大提升CSSer的工作效率,你无需考虑各种浏览器前缀兼,只需要按官方文档的书写方式去写,会得到加上浏览器前缀的代码,如下: .row { @include displ ...
 - 【swift学习笔记】五.使用枚举优雅的管理Segue
		
在做页面转跳的时候,我们要给Segue命名,如果Segue多了,管理他们就是一个恶梦.我们可以枚举更优雅的管理这些Segue. 1.我们先来建立一个protocol,他的功能就是让实现类实现一个Seg ...
 
随机推荐
- 水晶报表中用Code128制作条型码的方法
			
一.在[文件系统]中新建一个[Fonts文件夹],然后添加[Code128.ttf]文件. 二.在水晶报表里的[字段资源管理器]的[公式字段]中新建一个公式字段.点击[使用编辑器]之后弹出[公式工作室 ...
 - 升级Windows 2003域控制器到Windows 2012 R2
			
由于Windows 2003包括R2的扩展支持在今年7月14日就会过期.如果在扩展周期结束之前没有和微软签订昂贵服务协议,那么系统将得不到任何补丁和技术支持. 我这里准备了两台测试用的机器做这个实验. ...
 - python的三层架构
			
项目目录规范 Foo/ |-- core/ # 存放业务逻辑相关代码 | |-- core.py | |-- api/ # 存放接口文件,接口主要用于为业务逻辑提供数据操作. | |-- api.py ...
 - Elasticsearch6.2服务器升配后的bug
			
.suofang img { max-width: 100% !important; height: auto !important } 本篇文章记录最近一次生产服务器硬件升级之后引起集群不稳定的现象 ...
 - 第六章:Django 综合篇 - 11:分页 Paginator
			
分页功能是几乎所有的网站上都需要提供的功能,当你要展示的条目比较多时,必须进行分页,不但能减小数据库读取数据压力,也有利于用户浏览. Django又很贴心的为我们提供了一个Paginator分页工具, ...
 - Kubernetes 上部署应用-- 以Wordpress 为例
			
用一个 Wordpress 示例来尽可能将前面的知识点串联起来,我们需要达到的目的是让 Wordpress 应用具有高可用.滚动更新的过程中不能中断服务.数据要持久化不能丢失.当应用负载太高的时候能够 ...
 - haproxy + keeplived
			
两台主机: 192.168.2.163 192.168.2.165 # yum安装haproxy yum install haproxy # cat /etc/haproxy/haproxy.cfg ...
 - 引入Wukong让你的系统瞬间具备IOC能力
			
[Github源码] 本文重点要说的是如何通过引入Wukong第三方包让自己的系统能够拥有IOC容器能力,但在具体讲解步骤之前,还是想先简单的介绍一下什么是IOC以及它存在的意义:同时也就能清楚Wuk ...
 - proxy解决跨域问题
			
首先我们在本地开发,域名都是localhost,当我们需要请求后台数据时,就会出现跨域的问题 下面就是在vue.config.js配置文件里: devServer: { proxy: { ...
 - Mysql编程中遇到的小错误
			
我在mysql中创建的数据库表语句为如下 create table grade (id int not null, name varchar(255), desc varchar(255), prim ...