Asp.NetCore使用Efcore+Mysql实现CodeFirst,并且自动生成EfCore的builderModel的实现过程
主题:Asp.netcore Code First +DDD学习笔记
目录:
【一】 CodeFirst+DDD项目结构的构建
【二】Asp.Netcore使用Panda.DynamicWebApi来进行Controller解耦
【三】Asp.NetCore使用Efcore+Mysql实现CodeFirst
【四】EfCore实现全自动化迁移
案例代码下载点击
Domain和数据持久化的实现,Domain和Controller之间的链接本来就是可隔离的,所以直接分开就可以了,通过orm进行对数据的控制和持久化,这里采用Efcore+mysql实现codefirst来进行实现。
Domain和Efcore之间我们也不能过度耦合,所以这里不采用传统的手写efcontent,这里采用特性和反射来实现自动生成efcontent,链接数据库采用的是Asp mvc依赖注入。
Domain和Dto之间的映射。
1、EfModel特性的实现
在EfCoreRepository项目下添加一个文件夹命名为EfModelAttributes和一个类命名为EfEntityBuilder类、在EfModelAttributes该文件夹下添加一个类命名为EfModelAttribute,如下图:
EfModelAttribute代码的实现
using System;namespace EfCoreRepository.EfModelAttributes
{/// <summary>/// EFmodel的实体特性/// 作者:猴子 2019-09-19/// </summary>[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]public class EfModelAttribute: Attribute{}
}
EfEntityBuilder代码的实现
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Reflection;namespace EfCoreRepository
{/// <summary>/// ef实体的创建/// </summary>public static class EfEntityBuilder{/// <summary>/// 从程序反射添加实体配置/// </summary>/// <typeparam name="TAttribute"></typeparam>/// <param name="modelBuilder"></param>/// <param name="assembly"></param>public static void AddEntityConfigurationsFromAssembly<TAttribute>(this ModelBuilder modelBuilder, Assembly assembly)where TAttribute : Attribute{var autoTypes = assembly.GetTypes().Where(x => x.GetCustomAttribute<TAttribute>() != null && x.IsPublic);foreach (var entity in autoTypes){modelBuilder.Model.AddEntityType(entity);}foreach (var entity in modelBuilder.Model.GetEntityTypes()){var currentTableName = modelBuilder.Entity(entity.Name).Metadata.Relational().TableName;modelBuilder.Entity(entity.Name).ToTable(currentTableName.ToLower());var properties = entity.GetProperties();foreach (var property in properties)modelBuilder.Entity(entity.Name).Property(property.Name).HasColumnName(property.Name.ToLower());}}}
}
2、Domain里面的Efcontent的实现
在Domain这个项目里面添加三个文件夹分别命名为:CoreEntity(存放实体基类的),Domains(存放实体的),EfCoreContent(存放efcorecontent的)。在EfCoreContent下添加一个EfContent类,如下图:
EfContent的代码实现:
using EfCoreRepository;
using EfCoreRepository.EfModelAttributes;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;namespace Domain.EfCoreContent
{public class EfContent : DbContext{public EfContent(DbContextOptions<EfContent> options) : base(options){//部署时需要创建一个数据库实体版本管理表//每次需要执行创建迁移命令Add-Migration InitialCreate//执行前需要创建该表//CREATE TABLE `__EFMigrationsHistory` //(// `MigrationId` nvarchar(150) NOT NULL,// `ProductVersion` nvarchar(32) NOT NULL,// PRIMARY KEY(`MigrationId`)//);if (base.Database.GetPendingMigrations().Any()){base.Database.Migrate(); //执行迁移} }/ <summary>/ 因为使用无参构造器,所以需要自带链接字符串/ </summary>/ <param name="optionsBuilder"></param>//protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)//{// if (!optionsBuilder.IsConfigured)// optionsBuilder.UseMySQL("Database = 'studyddd'; Data Source = 'localhost'; User Id = 'root'; Password = ''; charset = 'utf8'; pooling = true; Allow Zero Datetime = True;Allow User Variables=True;TreatTinyAsBoolean=false");//} /// <summary>/// 创建实体/// </summary>/// <param name="modelBuilder"></param>protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.AddEntityConfigurationsFromAssembly<EfModelAttribute>(Assembly.GetExecutingAssembly());base.OnModelCreating(modelBuilder);}/// <summary>/// 扩展查询/// </summary>/// <typeparam name="T"></typeparam>/// <param name="predicate"></param>/// <returns></returns>public IQueryable<T> Where<T>(Expression<Func<T, bool>> predicate) where T : class{return base.Set<T>().Where(predicate);}}
}
3、在Api项目里面注入Efcontent
在Api的Startup文件里面的ConfigureServices方法添加如下代码
services.AddDbContextPool<EfContent>(options =>options.UseMySQL("Database = 'studyddd'; Data Source = 'localhost'; User Id = 'root'; Password = ''; charset = 'utf8'; pooling = true; Allow Zero Datetime = True;Allow User Variables=True;TreatTinyAsBoolean=false",b=>b.MigrationsAssembly("Api")), 200);
这时候,自动生成Efcontent已经完成。
4、创建一个实体进行测试
在Domain项目下的Domains文件夹里面创建Tests文件夹,在该文件夹下创建Test类如下
Test代码实现如下:
using EfCoreRepository.EfModelAttributes;
using System.ComponentModel.DataAnnotations;namespace Domain.Domains.Tests
{/// <summary>/// 测试类/// </summary>[EfModel]public class Test {[Key]public int Id { get; set; }public string HelloWorld { get; set; }}
}
注:这里的实体要打上刚刚我们做的EfModel特性,不打特性的不会被自动创建到Efcontent里面。
5、在Controller里面开始使用Test该实体进行数据存储和访问。
修改Controller项目里面的TestAppService这个类里面如下
using Controller.Core;
using Domain.EfCoreContent;
using System.Linq;namespace Controller.Controllers.Test
{/// <summary>/// 测试类/// </summary>public class TestAppService : AppService, IAppService{public EfContent EfContent { get; set; }public TestAppService(EfContent efContent){EfContent = efContent;}public string GetHelloWorld(){return "Hello World !!";}/// <summary>/// 添加一条测试数据/// </summary>/// <returns></returns>public string AddTest(){Domain.Domains.Tests.Test test = new Domain.Domains.Tests.Test();test.HelloWorld="你好世界";EfContent.Add(test);EfContent.SaveChanges();return "写入成功";}/// <summary>/// 获取一条数据/// </summary>/// <returns></returns>public Domain.Domains.Tests.Test GetTest(){return EfContent.Where<Domain.Domains.Tests.Test>(p => p.HelloWorld == "你好世界").FirstOrDefault(); ;}}
}
我实现了一个添加数据的接口,和一个查询数据的接口。
当然这时候我们还不能之间运行项目开始测试,这时候我们需要到数据库创建一个数据库版本管理表,运行如下sql
CREATE TABLE `__EFMigrationsHistory`
(
`MigrationId` nvarchar(150) NOT NULL,
`ProductVersion` nvarchar(32) NOT NULL,
PRIMARY KEY(`MigrationId`)
);
生成表后如图:
之后如果我们实体进行了修改或者调整,需要在【程序包管理控制台里面执行迁移命令】,每次修改或者添加都需要执行创建迁移命令
Add-Migration InitialCreate
注:程序包管理器执行命令时,要选择Api项目,如图
如何打开程序包管理控制台
执行命令成功后,你会发现在Api项目里面多了一个文件夹,如图:
该文件夹里面的内容不用管他,开始启动项目Api项目,打开swagger你会发现多了两个接口和一个Models,如下图
执行一次添加一条测试数据的接口,你会发现数据库里面自动多了一个test表,如下图
数据库:
执行下 查询接口:
到这里EfCore+Mysql CodeFirst方式已经实现。
6、Dto的使用:
Dto主要作用是控制返回和接入的数据,所以他和别的模块并没有一些很强的交合,主要就是一些实体。我们在Dto里面创建一个testdto如下图
TestDto的代码实现如下
namespace Dto.TestDtos
{public class TestDto{public string HelloWorld { get; set; } }
}
在Controller里面的使用,修改获取一条数据的接口如下:
/// <summary>/// 获取一条数据/// </summary>/// <returns></returns>public Dto.TestDtos.TestDto GetTest(){return EntityToDto<Dto.TestDtos.TestDto>(EfContent.Where<Domain.Domains.Tests.Test>(p => p.HelloWorld == "你好世界").FirstOrDefault()); }
这里使用之前在基类里面封装的一个实体到Dto的方法。
再次运行Api项目,使用Swagger执行 获取一条数据的接口会发现返回的数据Id消失了,如下:
总结:这里我发现该CodeFirst方式,有个美中不足的就是结构迁移,需要执行一下迁移命令,还不能全部代码化,后续需要进行优化,如果有大神有建议和方法,请大神不吝赐教。
github地址:https://github.com/houliren/Asp.netcore-Code-First-DDD