Entity Framework Core可以使用控制台命令为现有数据库生成模型代码和DbContext dotnet ef dbcontext scaffold。我们为什么不尝试在运行时生成DbContext?
在本文中,我将介绍如何在应用程序中的运行时:
- 使用EF Core生成DbContext代码。
- 使用Roslyn在内存中编译它。
- 下载生成的程序集。
- 创建一个生成的DbContext的实例。
- 通过收到的DbContext处理数据库。
github上有一个例子。
准备工作
该应用程序的平台将是NET Core 3.1.3。
例如,我将使用MS SQL数据库,我们需要一个连接字符串。但是,该方法本身适用于EF Core支持的任何数据库引擎(我测试了sqlite和postreg)。
让我们创建一个控制台应用程序,向其中添加必要的包:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis" Version="3.5.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.3" />
    <PackageReference Include="Bricelam.EntityFrameworkCore.Pluralizer" Version="1.0.0" />
  </ItemGroup>
</Project>
代码生成器位于软件包中Microsoft.EntityFrameworkCore.Design。如果通过软件包管理器控制台安装此软件包,则以下代码将添加到* .csproj中:
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
该代码说明[ 1 ],仅在开发过程中需要该程序包,而在运行时不使用该程序包。我们将在运行时需要它,因此我们需要像这样导入包:
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3" />
1.生成代码
在Entity Framework Core中,代码生成服务IReverseEngineerScaffolder:
interface IReverseEngineerScaffolder
{
    ScaffoldedModel ScaffoldModel(
        string connectionString, 
        //   
        DatabaseModelFactoryOptions databaseOptions, 
        //   
        ModelReverseEngineerOptions modelOptions, 
        //  ,  
        ModelCodeGenerationOptions codeOptions);
}
创建此服务实例的最简单方法是为其创建一个依赖注入容器。
:
IReverseEngineerScaffolder CreateMssqlScaffolder() => 
    new ServiceCollection()
        .AddEntityFrameworkSqlServer()
        .AddLogging()
        .AddEntityFrameworkDesignTimeServices()
        .AddSingleton<LoggingDefinitions, SqlServerLoggingDefinitions>()
        .AddSingleton<IRelationalTypeMappingSource, SqlServerTypeMappingSource>()
        .AddSingleton<IAnnotationCodeGenerator, AnnotationCodeGenerator>()
        .AddSingleton<IDatabaseModelFactory, SqlServerDatabaseModelFactory>()
        .AddSingleton<IProviderConfigurationCodeGenerator, SqlServerCodeGenerator>()
        .AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
        .AddSingleton<IPluralizer, Bricelam.EntityFrameworkCore.Design.Pluralizer>()
        .BuildServiceProvider()
        .GetRequiredService<IReverseEngineerScaffolder>();
IPluralizer . , .
PostgreSQLprivate IReverseEngineerScaffolder CreatePostgreScaffolder() => 
    new ServiceCollection()
        .AddEntityFrameworkNpgsql()
        .AddLogging()
        .AddEntityFrameworkDesignTimeServices()
        .AddSingleton<LoggingDefinitions, NpgsqlLoggingDefinitions>()
        .AddSingleton<IRelationalTypeMappingSource, NpgsqlTypeMappingSource>()
        .AddSingleton<IAnnotationCodeGenerator, AnnotationCodeGenerator>()
        .AddSingleton<IDatabaseModelFactory, NpgsqlDatabaseModelFactory>()
        .AddSingleton<IProviderConfigurationCodeGenerator, NpgsqlCodeGenerator>()
        .AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
        .AddSingleton<IPluralizer, Bricelam.EntityFrameworkCore.Design.Pluralizer>()
        .BuildServiceProvider()
        .GetRequiredService<IReverseEngineerScaffolder>();
 sqliteprivate IReverseEngineerScaffolder CreateSqliteScaffolder() => 
    new ServiceCollection()
        .AddEntityFrameworkSqlite()
        .AddLogging()
        .AddEntityFrameworkDesignTimeServices()
        .AddSingleton<LoggingDefinitions, SqliteLoggingDefinitions>()
        .AddSingleton<IRelationalTypeMappingSource, SqliteTypeMappingSource>()
        .AddSingleton<IAnnotationCodeGenerator, AnnotationCodeGenerator>()
        .AddSingleton<IDatabaseModelFactory, SqliteDatabaseModelFactory>()
        .AddSingleton<IProviderConfigurationCodeGenerator, SqliteCodeGenerator>()
        .AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
        .AddSingleton<IPluralizer, Bricelam.EntityFrameworkCore.Design.Pluralizer>()
        .BuildServiceProvider()
        .GetRequiredService<IReverseEngineerScaffolder>();
 :
var scaffolder = CreateMssqlScaffolder();
:
var dbOpts = new DatabaseModelFactoryOptions();
var modelOpts = new ModelReverseEngineerOptions(); 
var codeGenOpts = new ModelCodeGenerationOptions()
{
    
    RootNamespace = "TypedDataContext",
    ContextName = "DataContext",
    ContextNamespace = "TypedDataContext.Context",
    ModelNamespace = "TypedDataContext.Models",
    
    
    SuppressConnectionStringWarning = true
};
,
ScaffoldedModel scaffoldedModelSources =    
    scaffolder.ScaffoldModel(onnectionString, dbOpts, modelOpts, codeGenOpts);
:
class ScaffoldedModel
{
    
    public virtual ScaffoldedFile ContextFile { get; set; }
    
    public virtual IList<ScaffoldedFile> AdditionalFiles { get; }
}
Lazy Loading, UseLazyLoadingProxies() :
var contextFile = scaffoldedModelSources.ContextFile.Code
    .Replace(".UseSqlServer", ".UseLazyLoadingProxies().UseSqlServer");
, , .
2. Roslyn
Roslyn, :
CSharpCompilation GenerateCode(List<string> sourceFiles)
{
    var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8);
    var parsedSyntaxTrees = sourceFiles
        .Select(f => SyntaxFactory.ParseSyntaxTree(f, options));
    return CSharpCompilation.Create($"DataContext.dll",
        parsedSyntaxTrees,
        references: GetCompilationReferences(),
        options: new CSharpCompilationOptions(
            OutputKind.DynamicallyLinkedLibrary,
            optimizationLevel: OptimizationLevel.Release));
}
, :
List<MetadataReference> CompilationReferences()
{
    var refs = new List<MetadataReference>();
    
    
    var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
    refs.AddRange(referencedAssemblies.Select(a =>
        MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
    
    refs.Add(MetadataReference.CreateFromFile(
        typeof(object).Assembly.Location));
    refs.Add(MetadataReference.CreateFromFile(
        Assembly.Load("netstandard, Version=2.0.0.0").Location));
    refs.Add(MetadataReference.CreateFromFile(
        typeof(System.Data.Common.DbConnection).Assembly.Location));
    refs.Add(MetadataReference.CreateFromFile(
        typeof(System.Linq.Expressions.Expression).Assembly.Location))
    
    
    
    return refs;
}
:
MemoryStream peStream = new MemoryStream();
EmitResult emitResult = GenerateCode(sourceFiles).Emit(peStream);
, emitResult.Success true, peStream .
- , . emitResult .
3.
, , :
var assemblyLoadContext = new AssemblyLoadContext("DbContext", isCollectible);
var assembly = assemblyLoadContext.LoadFromStream(peStream);
isCollectible. , . NET Core 3 [2].
, , . [5]:
assemblyLoadContext.Unload();
LazyLoading, EF Core Proxy- , DefaultLoadContext, collectible. NonCollectible- collectible-, collectible LazyLoading. [3][4], .
4. DbContext
, DbContext.
var type = assembly.GetType("TypedDataContext.Context.DataContext");
var constructor = type.GetConstructor(Type.EmptyTypes);
DbContext dynamicContext = (DbContext)constructor.Invoke(null);
, :
public static class DynamicContextExtensions
{
    public static IQueryable Query(this DbContext context, string entityName) =>
        context.Query(context.Model.FindEntityType(entityName).ClrType);
    static readonly MethodInfo SetMethod =
        typeof(DbContext).GetMethod(nameof(DbContext.Set), 1, Array.Empty<Type>()) ??
        throw new Exception($"Type not found: DbContext.Set");
    public static IQueryable Query(this DbContext context, Type entityType) =>
        (IQueryable)SetMethod.MakeGenericMethod(entityType)?.Invoke(context, null) ??
        throw new Exception($"Type not found: {entityType.FullName}");
}
Reflection Set<> DbContext.
, :
foreach (var entityType in dynamicContext.Model.GetEntityTypes())
{
    var items = (IQueryable<object>)dynamicContext.Query(entityType.Name);
    Console.Write($"Entity type: {entityType.ClrType.Name} ");
    Console.WriteLine($"contains {items.Count()} items");
}
, , , .
ASP.NET Core Blazor , , SQL, MS SQL, PostrgreSQL, sqlite,
, EF Core DbContext runtime. NET Core — collectible assemblies, , .
github.
[1] (PackageReference)
[2] Collectible assemblies in .NET Core 3.0
[3] Lazy loading proxy doesn't support entity inside collectible assembly #18272
[4] 支持可收藏的动态装配体#473
[5] 如何在.NET Core中使用和调试程序集的可卸载性
感谢您的时间!
我还有其他一些话题要写。如果您在调查中指出您感兴趣的主题,将不胜感激。