【问题标题】:EF Core - Add new tables to database During RuntimeEF Core - 在运行时向数据库添加新表
【发布时间】:2017-04-23 17:24:30
【问题描述】:

我有一个 asp.net 核心项目,它需要能够在运行时支持插件,因此,我需要根据已插入的内容生成数据库表。每个插件都分为单独的项目和他们有自己的 DbContext 类。要使用的插件在编译时是未知的,只有在运行时才知道。

现在在 EF Core 中,我认为会有像“UpdateDatabase”这样的方法,您可以在其中将表添加到现有数据库中,但我错了。有没有办法做到这一点?我能够为每个插件生成一个单独的数据库,但这并不是我的想法。我需要一个数据库中的所有表。

这是“HRContext”插件的代码:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Plugins.HR.Models.Entities;

namespace Plugins.HR.Contexts
{
    public class HrContext : DbContext
    {
        public HrContext()
        {
        }
        public HrContext(DbContextOptions<HrContext> contextOptions) : base(contextOptions)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("HR");
            base.OnModelCreating(modelBuilder);
        }

        public DbSet<Address> Address { get; set; }
        public DbSet<Attendance> Attendance { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<JobTitle> JobTitles { get; set; }
    }
}

这是“CoreContext”插件的另一段代码:

using System;
using System.Collections.Generic;
using System.Text;
using Core.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace Core.Data.Contexts
{
    public class CoreContext : DbContext
    {
        public CoreContext()
        {

        }
        public CoreContext(DbContextOptions<CoreContext> contextOptions) : base(contextOptions)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("Core");
            base.OnModelCreating(modelBuilder);

        }
        public DbSet<Test> Tests { get; set; }
    }
}

我在 Startup.cs 中的 ConfigureServices 方法:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CoreContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.Core; integrated security = true;"))
    .AddDbContext<HrContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.HR; integrated security = true;"));

    // Add framework services.
    services.AddMvc();
}

如果我尝试将连接字符串更改为相同,迟早我会收到一条错误消息,指出一个插件的表不存在。我尝试了“EnsureCreated”,但也没有用。

【问题讨论】:

  • 我也在创建一个带有插件的项目,并且需要在运行时创建表 - EF 迁移不是一个选项。你找到解决办法了吗?
  • @Matt 很遗憾,没有。我只需要为我的项目使用不同的数据库来解决不同的插件。
  • 我找到了一个解决方案,并将其作为答案发布在下面。祝你好运

标签: c# asp.net entity-framework


【解决方案1】:

我遇到了同样的问题。几天前在 GitHub 上查看我的解决方案,这里:EF Core Issue #9238

您需要的是以下内容:

// Using an interface, so that we can swap out the implementation to support PG or MySQL, etc if we wish...
public interface IEntityFrameworkHelper
{
    void EnsureTables<TContext>(TContext context)
        where TContext : DbContext;
}

// Default implementation (SQL Server)
public class SqlEntityFrameworkHelper : IEntityFrameworkHelper
{
    public void EnsureTables<TContext>(TContext context)
        where TContext : DbContext
    {
        string script = context.Database.GenerateCreateScript(); // See issue #2943 for this extension method
        if (!string.IsNullOrEmpty(script))
        {
            try
            {
                var connection = context.Database.GetDbConnection();

                bool isConnectionClosed = connection.State == ConnectionState.Closed;

                if (isConnectionClosed)
                {
                    connection.Open();
                }

                var existingTableNames = new List<string>();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT table_name from INFORMATION_SCHEMA.TABLES WHERE table_type = 'base table'";

                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            existingTableNames.Add(reader.GetString(0).ToLowerInvariant());
                        }
                    }
                }

                var split = script.Split(new[] { "CREATE TABLE " }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string sql in split)
                {
                    var tableName = sql.Substring(0, sql.IndexOf("(", StringComparison.OrdinalIgnoreCase));
                    tableName = tableName.Split('.').Last();
                    tableName = tableName.Trim().TrimStart('[').TrimEnd(']').ToLowerInvariant();

                    if (existingTableNames.Contains(tableName))
                    {
                        continue;
                    }

                    try
                    {
                        using (var createCommand = connection.CreateCommand())
                        {
                            createCommand.CommandText = "CREATE TABLE " + sql.Substring(0, sql.LastIndexOf(";"));
                            createCommand.ExecuteNonQuery();
                        }
                    }
                    catch (Exception)
                    {
                        // Ignore
                    }
                }

                if (isConnectionClosed)
                {
                    connection.Close();
                }
            }
            catch (Exception)
            {
                // Ignore
            }
        }
    }
}

然后在Startup.Configure() 的末尾,我解析了一个IEntityFrameworkHelper 实例并将其与DbContext 的实例一起使用来调用EnsureTables()

一个问题是我仍然需要考虑脚本中不是 CREATE TABLE 语句的部分。例如,CREATE INDEX 语句。

我要求他们给我们一个干净的解决方案,例如:将CreateTable&lt;TEntity&gt;() 方法添加到IRelationalDatabaseCreator。不过,我并没有屏住呼吸......

编辑

我忘记发布GenerateCreateScript() 的代码。见下文:

using System.Text;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;

public static class DatabaseFacadeExtensions
{
    public static string GenerateCreateScript(this DatabaseFacade database)
    {
        var model = database.GetService<IModel>();
        var migrationsModelDiffer = database.GetService<IMigrationsModelDiffer>();
        var migrationsSqlGenerator = database.GetService<IMigrationsSqlGenerator>();
        var sqlGenerationHelper = database.GetService<ISqlGenerationHelper>();

        var operations = migrationsModelDiffer.GetDifferences(null, model);
        var commands = migrationsSqlGenerator.Generate(operations, model);

        var stringBuilder = new StringBuilder();
        foreach (var command in commands)
        {
            stringBuilder
                .Append(command.CommandText)
                .AppendLine(sqlGenerationHelper.BatchTerminator);
        }

        return stringBuilder.ToString();
    }
}

它基于此处找到的代码:EF Core Issue #2943

【讨论】:

    猜你喜欢
    • 2019-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-28
    • 2010-12-18
    • 1970-01-01
    • 2011-08-14
    相关资源
    最近更新 更多