Entity Framework Core 中的动态变化模式

Posted

技术标签:

【中文标题】Entity Framework Core 中的动态变化模式【英文标题】:Dynamically changing schema in Entity Framework Core 【发布时间】:2016-09-14 20:52:24 【问题描述】:

UPD here 是我解决问题的方法。虽然它可能不是最好的,但它对我有用。


我在使用 EF Core 时遇到问题。我想通过模式机制分离我项目数据库中不同公司的数据。我的问题是如何在运行时更改模式名称?我找到了similar question 关于这个问题,但它仍然没有得到答复,而且我有一些不同的情况。所以我有Resolve 方法,可以在必要时授予 db-context

public static void Resolve(IServiceCollection services) 
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<DomainDbContext>()
        .AddDefaultTokenProviders();
    services.AddTransient<IOrderProvider, OrderProvider>();
    ...

我可以在OnModelCreating 中设置架构名称,但是,正如之前发现的,这个方法只调用一次,所以我可以像这样全局设置架构名称

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

或通过属性直接在模型中

[Table("order", Schema = "public")]
public class Order...

但是如何在运行时更改架构名称?我为每个请求创建上下文,但首先我通过对数据库中模式共享表的请求来确定用户的模式名称。那么组织该机制的正确方法是什么:

    通过用户凭据找出架构名称; 从特定架构的数据库中获取用户特定数据。

谢谢。

附:我使用 PostgreSql,这就是小写表名的原因。

【问题讨论】:

您可以发布您对 IModelCacheKeyFactory 所做的解决方法吗? @Zinov 我已经完成了,但请小心。 ***.com/a/50529432/3272018 【参考方案1】:

您是否已经在 EF6 中使用过 EntityTypeConfiguration?

我认为解决方案是在 DbContext 类的 OnModelCreating 方法上使用实体映射,如下所示:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.Extensions.Options;

namespace AdventureWorksAPI.Models

    public class AdventureWorksDbContext : Microsoft.EntityFrameworkCore.DbContext
    
        public AdventureWorksDbContext(IOptions<AppSettings> appSettings)
        
            ConnectionString = appSettings.Value.ConnectionString;
        

        public String ConnectionString  get; 

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        
            optionsBuilder.UseSqlServer(ConnectionString);

            // this block forces map method invoke for each instance
            var builder = new ModelBuilder(new CoreConventionSetBuilder().CreateConventionSet());

            OnModelCreating(builder);

            optionsBuilder.UseModel(builder.Model);
        

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        
            modelBuilder.MapProduct();

            base.OnModelCreating(modelBuilder);
        
    

OnConfiguring 方法上的代码在 DbContext 类的每个实例创建时强制执行 MapProduct。

MapProduct方法的定义:

using System;
using Microsoft.EntityFrameworkCore;

namespace AdventureWorksAPI.Models

    public static class ProductMap
    
        public static ModelBuilder MapProduct(this ModelBuilder modelBuilder, String schema)
        
            var entity = modelBuilder.Entity<Product>();

            entity.ToTable("Product", schema);

            entity.HasKey(p => new  p.ProductID );

            entity.Property(p => p.ProductID).UseSqlServerIdentityColumn();

            return modelBuilder;
        
    

正如您在上面看到的,有一行可以为表设置架构和名称,您可以为 DbContext 中的一个构造函数发送架构名称或类似的东西。

请不要使用魔术字符串,您可以创建一个包含所有可用模式的类,例如:

using System;

public class Schemas

    public const String HumanResources = "HumanResources";
    public const String Production = "Production";
    public const String Sales = "Sales";

要使用特定架构创建 DbContext,您可以这样写:

var humanResourcesDbContext = new AdventureWorksDbContext(Schemas.HumanResources);

var productionDbContext = new AdventureWorksDbContext(Schemas.Production);

显然你应该根据模式的名称参数的值来设置模式名称:

entity.ToTable("Product", schemaName);

【讨论】:

正如我上面写的,OnModelCreating 中的模式名称没有问题,问题是这个方法只调用了一次,所以下一个创建的上下文将具有相同的模式。但也许我在你的回复中遗漏了一些重要的东西? 我明白你的意思,你说得对,我之前的答案不包括 OnModelCreating 问题的解决方案,但我在博客上搜索了这个问题的解决方案,我找到了 OnConfiguring 方法的代码,我修改了我的答案,请检查一下,如果有用请告诉我link 这可能适用于小型项目,但我无法想象为数百个表维护 MapProduct 方法,更不用说所有可能的设置和迁移...... 您说得很好,您是在谈论为这些方法编写代码还是维护大型代码文件? AppSettings 类来自哪个命名空间?谢谢。【参考方案2】:

对不起大家,我之前应该发布我的解决方案,但由于某种原因我没有发布,所以在这里。

但是

请记住,该解决方案可能有任何问题,因为它既没有经过任何人的审查,也没有经过生产验证,我可能会在这里得到一些反馈。

在项目中我使用了 ASP .NET Core 1


关于我的数据库结构。我有 2 个上下文。第一个包含有关用户的信息(包括他们应该处理的数据库方案),第二个包含用户特定的数据。

Startup.cs 我添加了两个上下文

public void ConfigureServices(IServiceCollection 
    services.AddEntityFrameworkNpgsql()
        .AddDbContext<SharedDbContext>(options =>
            options.UseNpgsql(Configuration["MasterConnection"]))
        .AddDbContext<DomainDbContext>((serviceProvider, options) => 
            options.UseNpgsql(Configuration["MasterConnection"])
                .UseInternalServiceProvider(serviceProvider));
...
    services.Replace(ServiceDescriptor.Singleton<IModelCacheKeyFactory, MultiTenantModelCacheKeyFactory>());
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

注意UseInternalServiceProvider部分,由Nero Sule建议,解释如下

在 EFC 1 发布周期的最后,EF 团队决定从默认服务集合 (AddEntityFramework().AddDbContext()) 中移除 EF 的服务,这意味着使用 EF 自己的服务提供者而不是解析服务应用服务提供者。

要强制 EF 使用您应用程序的服务提供者,您需要先将 EF 的服务与数据提供者一起添加到您的服务集合中,然后配置 DBContext 以使用内部服务提供者

现在我们需要MultiTenantModelCacheKeyFactory

public class MultiTenantModelCacheKeyFactory : ModelCacheKeyFactory 
    private string _schemaName;
    public override object Create(DbContext context) 
        var dataContext = context as DomainDbContext;
        if(dataContext != null) 
            _schemaName = dataContext.SchemaName;
        
        return new MultiTenantModelCacheKey(_schemaName, context);
    

DomainDbContext 是包含用户特定数据的上下文

public class MultiTenantModelCacheKey : ModelCacheKey 
    private readonly string _schemaName;
    public MultiTenantModelCacheKey(string schemaName, DbContext context) : base(context) 
        _schemaName = schemaName;
    
    public override int GetHashCode() 
        return _schemaName.GetHashCode();
    

我们还必须稍微改变上下文本身以使其具有模式感知能力:

public class DomainDbContext : IdentityDbContext<ApplicationUser> 
    public readonly string SchemaName;
    public DbSet<Foo> Foos get; set; 

    public DomainDbContext(ICompanyProvider companyProvider, DbContextOptions<DomainDbContext> options)
        : base(options) 
        SchemaName = companyProvider.GetSchemaName();
    
    protected override void OnModelCreating(ModelBuilder modelBuilder) 
        modelBuilder.HasDefaultSchema(SchemaName);
        base.OnModelCreating(modelBuilder);
    

并且共享上下文严格绑定到shared 架构:

public class SharedDbContext : IdentityDbContext<ApplicationUser> 
    private const string SharedSchemaName = "shared";
    public DbSet<Foo> Foos get; set; 
    public SharedDbContext(DbContextOptions<SharedDbContext> options)
        : base(options) 
    protected override void OnModelCreating(ModelBuilder modelBuilder) 
        modelBuilder.HasDefaultSchema(SharedSchemaName);
        base.OnModelCreating(modelBuilder);
    

ICompanyProvider 负责获取用户架构名称。是的,我知道它离完美的代码还有多远。

public interface ICompanyProvider 
    string GetSchemaName();


public class CompanyProvider : ICompanyProvider 
    private readonly SharedDbContext _context;
    private readonly IHttpContextAccessor _accesor;
    private readonly UserManager<ApplicationUser> _userManager;

    public CompanyProvider(SharedDbContext context, IHttpContextAccessor accesor, UserManager<ApplicationUser> userManager) 
        _context = context;
        _accesor = accesor;
        _userManager = userManager;
    
    public string GetSchemaName() 
        Task<ApplicationUser> getUserTask = null;
        Task.Run(() => 
            getUserTask = _userManager.GetUserAsync(_accesor.HttpContext?.User);
        ).Wait();
        var user = getUserTask.Result;
        if(user == null) 
            return "shared";
        
        return _context.Companies.Single(c => c.Id == user.CompanyId).SchemaName;
    

如果我没有错过任何东西,就是这样。现在,在经过身份验证的用户的每个请求中,都将使用正确的上下文。

希望对你有帮助。

【讨论】:

您在 OnModelCreating 期间设置了 DefaultSchema... 但该方法只调用一次。那么您如何根据请求动态更改架构?我不明白这个解决方案是如何工作的。 @JarrichVandeVoorde,模型由EF内部缓存,但在这种情况下,MultiTenantModelCacheKeyFactory配置EF,缓存根据当前公司(或与当前公司关联的架构)进行拆分。实例化dbcontext时,key不同,如果在缓存中找不到,将再次调用OnModelCreating。这种方法的不利方面是每个新公司的缓存都会增加(在我的情况下,这是内存溢出异常的原因) @user3272018,非常感谢。这是一个很棒的主意。我的方案是只创建模式并重新加载映射。一种。我正在查看 ModelSource 并试图清除 ModelSource 中的缓存。但是更改缓存键是一种更简单的方法:) b。 UseInternalServiceProvider() 很好,解决了在EF Core中,ServiceProvider会在DbContext正在初始化的时候被初始化的问题。【参考方案3】:

定义您的上下文并将架构传递给构造函数。

在 OnModelCreating 中设置默认架构。

   public class MyContext : DbContext , IDbContextSchema
    
        private readonly string _connectionString;
        public string Schema get;

        public MyContext(string connectionString, string schema)
        
            _connectionString = connectionString;
            Schema = schema;
        

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        
            if (!optionsBuilder.IsConfigured)
            
                optionsBuilder.ReplaceService<IModelCacheKeyFactory, DbSchemaAwareModelCacheKeyFactory>();
                optionsBuilder.UseSqlServer(_connectionString);
            

            base.OnConfiguring(optionsBuilder);
        

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        
            modelBuilder.HasDefaultSchema(Schema);
            
            // ... model definition ...
        
    

实现您的 IModelCacheKeyFactory。

public class DbSchemaAwareModelCacheKeyFactory : IModelCacheKeyFactory
    
        
        public object Create(DbContext context)
        
            return new 
                Type = context.GetType(),
                Schema = context is IDbContextSchema schema 
                    ? schema.Schema 
                    :  null
            ;
        
    

在 OnConfiguring 中,将 IModelCacheKeyFactory 的默认实现替换为您的自定义实现。

使用 IModelCacheKeyFactory 的默认实现,OnModelCreating 方法仅在第一次实例化上下文时执行,然后缓存结果。 更改实现,您可以修改 OnModelCreating 的结果如何被缓存和检索。在缓存键中包含模式,您可以为传递给上下文构造函数的每个不同模式字符串执行和缓存 OnModelCreating。

// Get a context referring SCHEMA1
var context1 = new MyContext(connectionString, "SCHEMA1");
// Get another context referring SCHEMA2
var context2 = new MyContext(connectionString, "SCHEMA2");

【讨论】:

这就是我下面的答案【参考方案4】:

有几种方法可以做到这一点:

在外部构建模型并通过DbContextOptionsBuilder.UseModel() 传入 将IModelCacheKeyFactory 服务替换为考虑架构的服务

【讨论】:

您能否提供一些详细信息或指向某些文档/博客/教程的链接? @user3272018 我有同样的问题,没有文档或示例如何在 EF Core 中正确实现 IModelCacheKeyFactory。 @tomas-voracek 哦,最终我做到了。稍后我将提供该代码作为自我回答。我确信这不是实现目标的完美方式,但它确实有效。也许甚至有人可以改进我的解决方案。对不起,我没有早点这样做。 嗨@user3272018 你能和我们分享你的解决方案吗?我正在尝试完成同样的事情。 @Diego,正如我在问题中提到的,我已经分享了我的解决方案here【参考方案5】:

我发现此博客可能对您有用。完美!:)

https://romiller.com/2011/05/23/ef-4-1-multi-tenant-with-code-first/

这个博客是基于ef4的,我不确定它是否可以与ef core一起工作。

public class ContactContext : DbContext

    private ContactContext(DbConnection connection, DbCompiledModel model)
        : base(connection, model, contextOwnsConnection: false)
     

    public DbSet<Person> People  get; set; 
    public DbSet<ContactInfo> ContactInfo  get; set; 

    private static ConcurrentDictionary<Tuple<string, string>, DbCompiledModel> modelCache
        = new ConcurrentDictionary<Tuple<string, string>, DbCompiledModel>();

    /// <summary>
    /// Creates a context that will access the specified tenant
    /// </summary>
    public static ContactContext Create(string tenantSchema, DbConnection connection)
    
        var compiledModel = modelCache.GetOrAdd(
            Tuple.Create(connection.ConnectionString, tenantSchema),
            t =>
            
                var builder = new DbModelBuilder();
                builder.Conventions.Remove<IncludeMetadataConvention>();
                builder.Entity<Person>().ToTable("Person", tenantSchema);
                builder.Entity<ContactInfo>().ToTable("ContactInfo", tenantSchema);

                var model = builder.Build(connection);
                return model.Compile();
            );

        return new ContactContext(connection, compiledModel);
    

    /// <summary>
    /// Creates the database and/or tables for a new tenant
    /// </summary>
    public static void ProvisionTenant(string tenantSchema, DbConnection connection)
    
        using (var ctx = Create(tenantSchema, connection))
        
            if (!ctx.Database.Exists())
            
                ctx.Database.Create();
            
            else
            
                var createScript = ((IObjectContextAdapter)ctx).ObjectContext.CreateDatabaseScript();
                ctx.Database.ExecuteSqlCommand(createScript);
            
        
    

这些代码的主要思想是提供一个静态方法,通过不同的模式创建不同的DbContext,并用一定的标识符缓存它们。

【讨论】:

【参考方案6】:

使用 EFCore 花了几个小时才弄清楚这一点。似乎对实现这一点的正确方法有很多困惑。我相信在 EFCore 中处理自定义模型的简单而正确的方法是替换默认的 IModelCacheKeyFactory 服务,如下所示。在我的示例中,我正在设置自定义表名。

    在您的上下文类中创建一个 ModelCacheKey 变量。 在上下文构造函数中,设置 ModelCacheKey 变量 创建一个继承自 IModelCacheKeyFactory 的类并使用 ModelCacheKey (MyModelCacheKeyFactory) 在 OnConfiguring 方法 (MyContext) 中,替换默认的 IModelCacheKeyFactory 在 OnModelCreating 方法 (MyContext) 中,使用 ModelBuilder 定义您需要的任何内容。
public class MyModelCacheKeyFactory : IModelCacheKeyFactory

    public object Create(DbContext context)
        => context is MyContext myContext ?
        (context.GetType(), myContext.ModelCacheKey) :
        (object)context.GetType();


public partial class MyContext : DbContext

     public string Company  get; 
     public string ModelCacheKey  get; 
     public MyContext(string connectionString, string company) : base(connectionString) 
      
         Company = company;
         ModelCacheKey = company; //the identifier for the model this instance will use
     

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     
         //This will create one model cache per key
         optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory();
     

     protected override void OnModelCreating(ModelBuilder modelBuilder)
     
         modelBuilder.Entity<Order>(entity => 
          
             //regular entity mapping 
         );

         SetCustomConfigurations(modelBuilder);
     

     public void SetCustomConfigurations(ModelBuilder modelBuilder)
     
         //Here you will set the schema. 
         //In my example I am setting custom table name Order_CompanyX

         var entityType = typeof(Order);
         var tableName = entityType.Name + "_" + this.Company;
         var mutableEntityType = modelBuilder.Model.GetOrAddEntityType(entityType);
         mutableEntityType.RemoveAnnotation("Relational:TableName");
         mutableEntityType.AddAnnotation("Relational:TableName", tableName);
     

结果是上下文的每个实例都会导致 efcore 基于 ModelCacheKey 变量进行缓存。

【讨论】:

【参考方案7】:

您可以在固定模式表上使用 Table 属性。

您不能在模式更改表上使用属性,您需要通过 ToTable fluent API 进行配置。 如果您禁用模型缓存(或者您编写自己的缓存),架构可以在每次请求时更改,因此您可以在上下文创建(每次)时指定架构。

这是基本思路

class MyContext : DbContext

    public string Schema  get; private set; 

    public MyContext(string schema) : base()
    

    

    // Your DbSets here
    DbSet<Emp> Emps  get; set; 

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        modelBuilder.Entity<Emp>()
            .ToTable("Emps", Schema);
    

现在,您可以通过一些不同的方法在创建上下文之前确定架构名称。 例如,您可以将“系统表”放在不同的上下文中,这样每次请求时,您都可以使用系统表从用户名中检索架构名称,然后在正确的架构上创建工作上下文(您可以在上下文之间共享表)。 您可以将系统表从上下文中分离出来,并使用 ADO .Net 来访问它们。 可能还有其他几种解决方案。

你也可以看这里Multi-Tenant With Code First EF6

你可以谷歌ef multi tenant

编辑 还有模型缓存的问题(我忘了)。 您必须禁用模型缓存或更改缓存的行为。

【讨论】:

感谢您的回复,但 as far as I know OnModelCreating 方法只调用了一次,我无法在每次请求时通过它更改架构名称。其实我用的是 EF Core,但是OnModelCreating 的行为是一样的。 @bricelam 谈到了解决我的问题的两种方法,所以我对一些更详细的解释感兴趣,因为 EF 的开发人员应该比其他人更了解 EF,嗯? 在您发布的 SO 问题中,IDbModelCacheKeyProvider 似乎类似于 IModelCacheKeyFactory。也许这是解决我问题的关键。我错过了那篇文章——谢谢。 你是对的!!!我忘记了模型缓存!还要注意性能(你可以在周围找到一些文章,也可以在 Stack Overflow 上)【参考方案8】:

也许我这个答案有点晚了

我的问题是处理具有相同结构的不同架构,比如说多租户。

当我尝试为不同的架构创建相同上下文的不同实例时,Entity frameworks 6 开始发挥作用,捕捉到第一次创建 dbContext 然后对于以下实例,它们使用不同的架构名称创建,但 onModelCreating 是从不调用意味着每个实例都指向相同的先前捕获的预生成视图,指向第一个模式。

然后我意识到,为每个模式创建一个从 myDBContext 继承的新类将通过克服实体框架捕获问题来解决我的问题,为每个模式创建一个新的新上下文,但随之而来的问题是我们将以硬编码模式结束,导致当我们需要添加另一个模式时,代码可伸缩性方面的另一个问题,必须添加更多类并重新编译和发布应用程序的新版本。

所以我决定在运行时进一步创建、编译和添加类到当前解决方案。

这里是代码

public static MyBaseContext CreateContext(string schema)

    MyBaseContext instance = null;
    try
    
        string code = $@"
            namespace MyNamespace
            
                using System.Collections.Generic;
                using System.Data.Entity;

                public partial class schemaContext : MyBaseContext
                
                    public schemaContext(string SCHEMA) : base(SCHEMA)
                    
                    

                    protected override void OnModelCreating(DbModelBuilder modelBuilder)
                    
                        base.OnModelCreating(modelBuilder);
                    
                
            
        ";

        CompilerParameters dynamicParams = new CompilerParameters();

        Assembly currentAssembly = Assembly.GetExecutingAssembly();
        dynamicParams.ReferencedAssemblies.Add(currentAssembly.Location);   // Reference the current assembly from within dynamic one
                                                                            // Dependent Assemblies of the above will also be needed
        dynamicParams.ReferencedAssemblies.AddRange(
            (from holdAssembly in currentAssembly.GetReferencedAssemblies()
             select Assembly.ReflectionOnlyLoad(holdAssembly.FullName).Location).ToArray());

        // Everything below here is unchanged from the previous
        CodeDomProvider dynamicLoad = CodeDomProvider.CreateProvider("C#");
        CompilerResults dynamicResults = dynamicLoad.CompileAssemblyFromSource(dynamicParams, code);

        if (!dynamicResults.Errors.HasErrors)
        
            Type myDynamicType = dynamicResults.CompiledAssembly.GetType($"MyNamespace.schemaContext");
            Object[] args =  schema ;
            instance = (MyBaseContext)Activator.CreateInstance(myDynamicType, args);
        
        else
        
            Console.WriteLine("Failed to load dynamic assembly" + dynamicResults.Errors[0].ErrorText);
        
    
    catch (Exception ex)
    
        string message = ex.Message;
    
    return instance;

我希望这可以帮助某人节省一些时间。

【讨论】:

【参考方案9】:

MVC Core 2.1 更新

您可以从具有多个模式的数据库创建模型。该系统的命名有点与模式无关。相同命名的表会附加一个“1”。 “dbo”是假定的架构,因此您无需通过 PM 命令在表名前添加任何内容

您必须自己重命名模型文件名和类名。

在 PM 控制台中

Scaffold-DbContext "Data Source=localhost;Initial Catalog=YourDatabase;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -force -Tables TableA, Schema1.TableA

【讨论】:

【参考方案10】:

我实际上发现它是一个使用 EF 拦截器的更简单的解决方案。

我其实保留了onModeling方法:

  protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.HasDefaultSchema("dbo"); // this is important to always be dbo

        // ... model definition ...
    

并且这段代码将在 Startup 中:

    public void ConfigureServices(IServiceCollection services)
    
        // if I add a service I can have the lambda (factory method) to read from request the schema (I put it in a cookie)
        services.AddScoped<ISchemeInterceptor, SchemeInterceptor>(provider =>
        
            var context = provider.GetService<IHttpContextAccessor>().HttpContext;

            var scheme = "dbo";
            if (context.Request.Cookies["schema"] != null)
            
                scheme = context.Request.Cookies["schema"];
            

            return new SchemeInterceptor(scheme);
        );

        services.AddDbContext<MyContext>(options =>
        
            var sp = services.BuildServiceProvider();
            var interceptor = sp.GetService<ISchemeInterceptor>();
            options.UseSqlServer(Configuration.GetConnectionString("Default"))
                .AddInterceptors(interceptor);
        );

拦截器代码看起来像这样(但基本上我们使用的是 ReplaceSchema):

public interface ISchemeInterceptor : IDbCommandInterceptor




public class SchemeInterceptor : DbCommandInterceptor, ISchemeInterceptor

    private readonly string _schema;

    public SchemeInterceptor(string schema)
    
        _schema = schema;
    

    public override Task<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result,
        CancellationToken cancellationToken = new CancellationToken())
    
        ReplaceSchema(command);
        return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
    

    public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
    
        ReplaceSchema(command);
        return base.ScalarExecuting(command, eventData, result);
    

    public override Task<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result,
        CancellationToken cancellationToken = new CancellationToken())
    
        ReplaceSchema(command);
        return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
    

    public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
    
        ReplaceSchema(command);
        return base.NonQueryExecuting(command, eventData, result);
    

    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<DbDataReader> result)
    
        ReplaceSchema(command);
        return result;
    

    public override Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result,
        CancellationToken cancellationToken = new CancellationToken())
    
        ReplaceSchema(command);
        return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
    

    private void ReplaceSchema(DbCommand command)
    
        command.CommandText = command.CommandText.Replace("[dbo]", $"[_schema]");
    

    public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
    
        // here you can handle cases like schema not found
        base.CommandFailed(command, eventData);
    

    public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData,
        CancellationToken cancellationToken = new CancellationToken())
    
        // here you can handle cases like schema not found
        return base.CommandFailedAsync(command, eventData, cancellationToken);
    



【讨论】:

【参考方案11】:

如果数据库之间的唯一区别是架构名称,那么解决问题的最简单方法是删除在 OnModelCreating 方法中设置默认架构的代码行:

protected override void OnModelCreating(ModelBuilder modelBuilder)

    ...
    modelBuilder.HasDefaultSchema("YourSchemaName"); <-- remove or comment this line
    ...

在这种情况下,EF Core 运行的底层 sql 查询不会在其 FROM 子句中包含架构名称。然后,您将能够编写一个方法,该方法将根据您的自定义条件设置正确的 DbContext。 这是我用来连接到具有相同数据库结构的不同 Oracle 数据库的示例(简而言之,假设在 Oracle 模式中与用户相同)。如果您使用的是另一个数据库,您只需输入正确的连接字符串,然后对其进行修改。

private YourDbContext SetDbContext()

    string connStr = @"Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=server_ip)(PORT=1521)))(CONNECT_DATA=(SID = server_sid)));User Id=server_user ;Password=server_password";

    //You can get db connection details e.g. from app config
    List<string> connections = config.GetSection("DbConneections");
    string serverIp;
    string dbSid;
    string dBUser;
    string dbPassword;

    /* some logic to choose a connection from config and set up string variables for a connection*/

    connStr = connStr.Replace("server_ip", serverIp);
    connStr = connStr.Replace("server_sid", dbSid);
    connStr = connStr.Replace("server_user", dBUser);
    connStr = connStr.Replace("server_password", dbPassword);

    var dbContext = dbContextFactory.CreateDbContext();
    dbContext.Database.CloseConnection();
    dbContext.Database.SetConnectionString(connStr);

    return dbContext;

最后,您将能够在需要调用此方法之前设置所需的 dbContext,您还可以将一些参数传递给该方法以帮助您选择正确的 db。

【讨论】:

如何设置 desired 架构名称? @GertArnold 我编辑了我的答案,看看。 仍然与设置架构无关。检查此问题上下文中“模式”一词的含义。 有时人们倾向于通过“添加”一些对他们有帮助的东西来解决他们的问题,而似乎忽略了基于“减法”的解决方案。在这个问题中,我可以想象一个人可能会遇到标题中描述的这种问题,正如我在回答中提到的,如果唯一的区别是模式名称,那么你基本上有不同的数据库具有相同的实体,你可以摆脱通过 EF Core 设置架构并解决了这个问题,无论如何它解决了我的问题,所以我决定分享这种方法。 由于多租户,您似乎没有意识到 OP 需要 设置架构。没有架构前缀的查询不符合要求,在运行时更改连接字符串不是问题。

以上是关于Entity Framework Core 中的动态变化模式的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 2 中的 ConnectionString 生成器

根据 ASP.NET Core 和 Entity Framework Core 中的条件禁用 [必需] 属性

Entity Framework Core 中的动态数据模型

Entity Framework Core 中的动态查询执行

无法更新 Entity Framework Core 中的标识列

ASP.NET Core 和 Entity Framework Core:Linq 中的左(外)连接