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 knowOnModelCreating
方法只调用了一次,我无法在每次请求时通过它更改架构名称。其实我用的是 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 中的动态查询执行