Skip to content

9.21 模型生成数据库

开始之前Furion.Web.Entry 层需要安装 Microsoft.EntityFrameworkCore.Tools 包。

9.21.1 数据库开发方式

Furion 提供两种主要方法来 保持实体模型和数据库架构同步

至于我们应该选用哪个方法,请确定你是希望以实体模型为准还是以数据库为准:

  • 如果希望 以实体模型为准,请使用正向工程(Code First)。 对实体模型进行更改时,此方法会以增量方式将相应架构更改应用到数据库,以使数据库保持与实体模型兼容。
  • 如果希望 以数据库架构为准,请使用反向工程(Database First)。 使用此方法,可通过将数据库架构反向工程到实体模型来生成相应的实体类型。

本章节是 正向工程(Code First) 的相关内容。

9.21.2 操作指南

特别注意Furion 框架默认数据迁移的程序集为:Furion.Database.Migrations,所以如果您改了程序集名称或通过 NuGet 方式安装的 Furion 包,则需要配置迁移程序集名称:

services.AddDatabaseAccessor(options =>  
{  
    options.AddDbPool<FurionDbContext>(DbProvider.Sqlite);  
}, "存放迁移文件的项目名称");  

另外,如果应用中配置了多个数据库上下文,那么所有的 迁移命令 都需要指定 -Context 数据库上下文名称 参数。如:

Add-Migration v1.0.0 -Context FurionDbContext  

9.21.2.1 创建实体模型 Person

using Furion.DatabaseAccessor;  
using System;  
using System.Collections.Generic;  
using System.ComponentModel.DataAnnotations;  

namespace Furion.Core  
{  
    public class Person : Entity  
    {  
        /// <summary>  
        /// 构造函数  
        /// </summary>  
        public Person()  
        {  
            CreatedTime = DateTime.Now;  
            IsDeleted = false;  
        }  

        /// <summary>  
        /// 姓名  
        /// </summary>  
        [MaxLength(32)]  
        public string Name { get; set; }  

        /// <summary>  
        /// 年龄  
        /// </summary>  
        public int Age { get; set; }  

        /// <summary>  
        /// 住址  
        /// </summary>  
        public string Address { get; set; }  
    }  
}  

实体约定所有数据库实体必须直接或间接继承 IEntity 接口。

9.21.2.2 打开 程序包管理控制台

9.21.2.3 切换默认项目

程序包管理控制台 默认项目设置为 Furion.Database.Migrations

9.21.2.4 创建模型版本

Add-Migration v1.0.0  

特别说明v1.0.0 是此处数据库更改的版本号,可以写任何字符串,但推荐写版本号,每次 +1

最终命令如下:

PM> Add-Migration v1.0.0  
Build started...  
Build succeeded.  
Microsoft.EntityFrameworkCore.Model.Validation[10400]  
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data, this mode should only be enabled during development.  
Microsoft.EntityFrameworkCore.Infrastructure[10403]  
      Entity Framework Core 5.0.0-rc.1.20451.13 initialized 'FurionDbContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: SensitiveDataLoggingEnabled DetailedErrorsEnabled MaxPoolSize=100 MigrationsAssembly=Furion.Database.Migrations  
To undo this action, use Remove-Migration.  
PM>  

生成成功后,Furion.Database.Migrations 项目下会新增 Migrations 文件夹(如果没有),同时本次的架构生成文件,如:

9.21.2.5 更新到数据库

Update-Database  

执行该命令后,数据库就会自动根据模型生成对应的表。

小知识如果 Update-Database 后面带字符串参数,则会自动还原数据库到指定版本,如:

Update-Database v0.0.3  

将数据库还原到 v0.0.3 版本

9.21.3 更新模型

如果模型改变了,重复上面操作即可,如:

Add-Migration v1.0.1  

Update-Database  

9.21.4 导出 Sql

有些时候,我们没有直接更新数据库的权限,或者怕出问题,我们都会先生成 Sql 看看,这时候只需要通过 Script-Migration 导出即可,如:

Script-Migration  

9.21.5 VS Code/Rider/任何IDE/操作系统 方式

9.21.5.1 安装 dotnet ef

dotnet tool install --global dotnet-ef --version 5.0.0-rc.2.20475.6  

9.21.5.2 cd 目录

通过 VS Code 打开 .sln 所在的目录,如:framework

之后进入 Furion.Database.Migrations 目录

cd Furion.Database.Migrations  

9.21.5.3 执行命令

dotnet ef migrations add v1.0.0 -s "../Furion.Web.Entry"  

dotnet ef database update -s "../Furion.Web.Entry"  

9.21.6 应用启动时自动生成数据库

Furion 框架建议大家使用命令方式操作数据库,完全不推荐自动化生成数据库,但是有些特殊情况下,有这个必要,故将此功能写出:

9.21.6.1 对已经生成 Migrations 文件情况

如果已经生成 Migrations 文件,那么可以直接在 Startup.cs 代码中实现程序启动时自动执行 update-database 命令,如:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
{  
    // 判断开发环境!!!必须!!!!  
    if (env.IsDevelopment())  
    {  
        Scoped.Create((_, scope) =>  
        {  
            var context = scope.ServiceProvider.GetRequiredService<FurionDbContext>();  
            context.Database.Migrate();  
        });  
    }  

    // 其他代码  
}  

9.21.6.2 如果没有生成过 Migrations 文件情况

public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
{  
    // 判断开发环境!!!必须!!!!  
    if (env.IsDevelopment())  
    {  
        Scoped.Create((_, scope) =>  
        {  
            var context = scope.ServiceProvider.GetRequiredService<FurionDbContext>();  
            context.Database.EnsureCreated();  
        });  
    }  

    // 其他代码  
}  

如果需要在创建数据库之前先删除旧的,可先调用 context.Database.EnsureDeleted(); 代码。慎重!!!!!!!!!!!!

9.21.7 MySql.EntityFrameworkCore.NET 6.0.8+ 问题

.NET 6.0.8+ 版本,微软底层修改了 IDesignTimeServices 逻辑导致 MySql.EntityFrameworkCore 版本没有及时更新导致一下错误:

PM> Add-Migration v0.0.1  
Build started...  
Build succeeded.  
Microsoft.EntityFrameworkCore.Model.Validation[10400]  
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.  
Microsoft.EntityFrameworkCore.Infrastructure[10403]  
      Entity Framework Core 6.0.8 initialized 'DefaultDbContext' using provider 'MySql.EntityFrameworkCore:6.0.4+MySQL8.0.30' with options: SensitiveDataLoggingEnabled DetailedErrorsEnabled MaxPoolSize=100 MigrationsAssembly=Furion.TestMS.Database.Migrations  
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Storage.TypeMappingSourceDependencies' while attempting to activate 'MySql.EntityFrameworkCore.Storage.Internal.MySQLTypeMappingSource'.  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)  
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)  
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)  
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)  
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)  
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)  
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)  
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)  
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)  
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()  
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()  
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)  
Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Storage.TypeMappingSourceDependencies' while attempting to activate 'MySql.EntityFrameworkCore.Storage.Internal.MySQLTypeMappingSource'.  
PM>  

解决办法也很简单,只需要在启动层添加 MysqlEntityFrameworkDesignTimeServices.cs 并写入以下内容即可:

using Microsoft.EntityFrameworkCore.Design;  
using MySql.EntityFrameworkCore.Extensions;  

namespace YourProject.Web.Entry;  

public class MysqlEntityFrameworkDesignTimeServices : IDesignTimeServices  
{  
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)  
    {  
        serviceCollection.AddEntityFrameworkMySQL();  
        new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection)  
            .TryAddCoreServices();  
    }  
}  

相关 Issue 讨论:https://gitee.com/dotnetchina/Furion/issues/I5O5ER

9.21.8 反馈与建议

与我们交流给 Furion 提 Issue


了解更多想了解更多 正向工厂 知识可查阅 EF Core - 管理数据库架构 章节。