【问题标题】:Generate DbContext and Models on runtime using connection string Entity Framework使用连接字符串实体框架在运行时生成 DbContext 和模型
【发布时间】:2020-03-06 09:50:37
【问题描述】:

有什么方法可以在运行时仅使用连接字符串生成 DbContext 吗?

我们可以使用 Scaffold 在实体框架核心上生成现有数据库的模型和 DbContext,它们必须从包管理器控制台或 Power Shell 运行。基于此,我想到了一个想法,在哪里可以在运行时从控制台应用程序执行 Scaffold 命令,其中应用程序将提供动态连接字符串。

非常感谢任何替代或好的建议。

【问题讨论】:

  • 你会坚持一个连接还是在需要时改变它?
  • 想经常换。
  • 如果你要问的是在运行时在同一个 dbcontext 上动态传递不同的连接字符串是可能的
  • 不,你错了,我注意到脚手架和现有数据库,这意味着数据库优先。我将手动更改连接字符串。
  • 如何针对运行时生成的 dbcontext 进行编码?

标签: c# entity-framework entity-framework-core


【解决方案1】:

是的,这是可能的。 Roslyn & EF Core: runtime DbContext constructing

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace RuntimeEfCore
{
    class Program
    {
        static void Main(string[] args)
        {
            var connectionString = args.Length > 0
                ? args[0]
                : throw new Exception("Pass connection string as a first parameter");

            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
            };

            var scaffoldedModelSources = scaffolder.ScaffoldModel(connectionString, dbOpts, modelOpts, codeGenOpts);
            var sourceFiles = new List<string> { scaffoldedModelSources.ContextFile.Code };
            sourceFiles.AddRange(scaffoldedModelSources.AdditionalFiles.Select(f => f.Code));

            using var peStream = new MemoryStream();

            var enableLazyLoading = false;
            var result = GenerateCode(sourceFiles, enableLazyLoading).Emit(peStream);

            if (!result.Success)
            {
                var failures = result.Diagnostics
                    .Where(diagnostic => diagnostic.IsWarningAsError || 
                                        diagnostic.Severity == DiagnosticSeverity.Error);

                var error = failures.FirstOrDefault();
                throw new Exception($"{error?.Id}: {error?.GetMessage()}");
            }

            var assemblyLoadContext = new AssemblyLoadContext("DbContext", isCollectible: !enableLazyLoading);

            peStream.Seek(0, SeekOrigin.Begin);
            var assembly = assemblyLoadContext.LoadFromStream(peStream);

            var type = assembly.GetType("TypedDataContext.Context.DataContext");
            _ = type ?? throw new Exception("DataContext type not found");

            var constr = type.GetConstructor(Type.EmptyTypes);
            _ = constr ?? throw new Exception("DataContext ctor not found");

            DbContext dynamicContext = (DbContext)constr.Invoke(null);
            var entityTypes = dynamicContext.Model.GetEntityTypes();

            Console.WriteLine($"Context contains {entityTypes.Count()} types");

            foreach (var entityType in dynamicContext.Model.GetEntityTypes())
            {
                var items = (IQueryable<object>)dynamicContext.Query(entityType.Name);

                Console.WriteLine($"Entity type: {entityType.ClrType.Name} contains {items.Count()} items");
            }

            Console.ReadKey();

            if (!enableLazyLoading)
            {
                assemblyLoadContext.Unload();
            }
        }

        static 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>();


        static List<MetadataReference> CompilationReferences(bool enableLazyLoading)
        {
            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));

            if (enableLazyLoading)
            {
                refs.Add(MetadataReference.CreateFromFile(typeof(ProxiesExtensions).Assembly.Location));
            }

            return refs;
        }

        private static CSharpCompilation GenerateCode(List<string> sourceFiles, bool enableLazyLoading)
        {
            var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8);

            var parsedSyntaxTrees = sourceFiles.Select(f => SyntaxFactory.ParseSyntaxTree(f, options));

            return CSharpCompilation.Create($"DataContext.dll",
                parsedSyntaxTrees,
                references: CompilationReferences(enableLazyLoading),
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
                    optimizationLevel: OptimizationLevel.Release,
                    assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
        }
    }

    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}");
    }
}

【讨论】:

    【解决方案2】:

    对于 EF 5,我的解决方案是运行附加到项目预构建事件的 EF CLI 命令。

    if $(ConfigurationName) == TEST1 (
    
    dotnet ef dbcontext scaffold name=dbConnStringName Microsoft.EntityFrameworkCore.SqlServer --startup-project ../C2C-PublicApi/C2C.PublicApi.Api.csproj -p ./C2C.PublicApi.Data.csproj -o Entities -n C2C.PublicApi.Data.Entities --context-dir ./DataContexts --context-namespace C2C.PublicApi.Data.DataContexts -c C2CApiDbContext -t [dbo].[tbl_AnaestheticType] -t [dbo].[tbl_Appointment] -t [dbo].[tbl_AppointmentCancellationReason] -t [dbo].[tbl_AppointmentCancellationSource] -t [dbo].[tbl_AppointmentConsultType] -t [dbo].[tbl_AppointmentType] -t [dbo].[tbl_BillingAccountType] --no-pluralize --no-onconfiguring **--no-build** --force
    
    )
    

    您很可能会在数据库的构建事件之前运行它。此外,假设您的项目已更新且不需要重新编译,则包含 --no-build 选项也很重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多