Asp.NetCore使用Efcore+Mysql实现CodeFirst,并且自动生成EfCore的builderModel的实现过程
主题:Asp.netcore Code First +DDD学习笔记
目录:
【二】Asp.Netcore使用Panda.DynamicWebApi来进行Controller解耦
【三】Asp.NetCore使用Efcore+Mysql实现CodeFirst
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