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中使用和调试程序集的可卸载性
感谢您的时间!
我还有其他一些话题要写。如果您在调查中指出您感兴趣的主题,将不胜感激。