热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

.net如何优雅的使用EFCore

.net如何优雅的使用EFCore EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作。功能非常强大,在老版本的时候叫做EF,后来.netcore问世,E

.net如何优雅的使用EFCore

 

EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作。功能非常强大,在老版本的时候叫做EF,后来.net core问世,EFCore也随之问世。
本文我们将用一个控制台项目Host一个web服务,并且使用本地Mysql作为数据库,使用EFCore的Code First模式进行数据操作。

 

目录



  • DBSet清除计划

  • IEntityTypeConfiguration(表配置)

  • Repository(仓储)

  • Autofac

  • 数据库配置

  • 项目架构和源码

 


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 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()获取用户的DBSet。


IEntityTypeConfiguration(表配置)

用数据库创建过表的同学都知道,在设计表的时候,可以给表添加很多配置和约束,在Code First模式中,很多同学都是在对象中通过注解的方式配置字段。如下就配置了用户名是不能为NULL和最大长度为500

[Required]
[MaxLength(500)]
public string UserName { get; set; }

也有的同学在DbContext中的OnModelCreating方法配置

modelBuilder.Entity().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 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 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
{
public override void Configure(EntityTypeBuilder 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(false);//导航属性,本质就是创建外键,虽然查询很方便,生产中不建议使用!!!
}
}

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 All();
IQueryable All(string[] propertiesToInclude);
IQueryable Where(Expressionbool>> filter);
IQueryable Where(Expressionbool>> 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 All()
{
return All(null);
}
public virtual IQueryable All(string[] propertiesToInclude)
{
var query = _dbContext.Set().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(); //注入domain模块
builder.RegisterGeneric(typeof(GenericRepository<,>))//将dbcontext注入到仓储的构造中
.UsingConstructor(typeof(LibraryDbContext))
.AsImplementedInterfaces()
.InstancePerDependency();
builder.RegisterType().As().InstancePerDependency();
}
}

2.Domain注入EFEntityInfo

public class EFCoreEleganceUseDomainModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType().SingleInstance();
}
}

数据库配置

1.注入DBContext,从配置文件读取数据库配置,然后根据开发/生产环境做一些特殊处理

var mysqlCOnfig= hostContext.Configuration.GetSection("Mysql").Get<MysqlOptions>();
var serverVersion = new MariaDbServerVersion(new Version(mysqlConfig.Version));
services.AddDbContext<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页面。

 

转 https://www.cnblogs.com/qwqwQAQ/p/16932139.html



推荐阅读
  • Vue 2 中解决页面刷新和按钮跳转导致导航栏样式失效的问题
    本文介绍了如何通过配置路由的 meta 字段,确保 Vue 2 项目中的导航栏在页面刷新或内部按钮跳转时,始终保持正确的 active 样式。具体实现方法包括设置路由的 meta 属性,并在 HTML 模板中动态绑定类名。 ... [详细]
  • 本文探讨了如何通过最小生成树(MST)来计算严格次小生成树。在处理过程中,需特别注意所有边权重相等的情况,以避免错误。我们首先构建最小生成树,然后枚举每条非树边,检查其是否能形成更优的次小生成树。 ... [详细]
  • 本文详细分析了JSP(JavaServer Pages)技术的主要优点和缺点,帮助开发者更好地理解其适用场景及潜在挑战。JSP作为一种服务器端技术,广泛应用于Web开发中。 ... [详细]
  • 本文详细介绍了如何使用libpq库与PostgreSQL后端建立连接。通过探讨PQconnectdb()函数的工作原理及其在实际应用中的使用方法,帮助读者理解并掌握建立高效、稳定的数据库连接的关键步骤。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Windows服务与数据库交互问题解析
    本文探讨了在Windows 10(64位)环境下开发的Windows服务,旨在定期向本地MS SQL Server (v.11)插入记录。尽管服务已成功安装并运行,但记录并未正确插入。我们将详细分析可能的原因及解决方案。 ... [详细]
  • SQL中UPDATE SET FROM语句的使用方法及应用场景
    本文详细介绍了SQL中UPDATE SET FROM语句的使用方法,通过具体示例展示了如何利用该语句高效地更新多表关联数据。适合数据库管理员和开发人员参考。 ... [详细]
  • Navicat Premium 15 安装指南及数据库连接配置
    本文详细介绍 Navicat Premium 15 的安装步骤及其对多种数据库(如 MySQL 和 Oracle)的支持,帮助用户顺利完成软件的安装与激活。 ... [详细]
  • 深入理解 Oracle 存储函数:计算员工年收入
    本文介绍如何使用 Oracle 存储函数查询特定员工的年收入。我们将详细解释存储函数的创建过程,并提供完整的代码示例。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • CSS 布局:液态三栏混合宽度布局
    本文介绍了如何使用 CSS 实现液态的三栏布局,其中各栏具有不同的宽度设置。通过调整容器和内容区域的属性,可以实现灵活且响应式的网页设计。 ... [详细]
  • IT项目管理过程中的方法、工具、技术
    工欲善其事,必先利其器。而对于一个软件开发项目,最重要的器就是方法,工具和技术。而这三要素中重要的又是方法论,方法是基础&# ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
author-avatar
悠哉游_甘肃
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有