实体框架的模型缓存使得它对大量数据库模式毫无用处

gel*_*upa 5 c# entity-framework multi-tenant

我使用的SaaS产品具有一定的用户群.到目前为止,我们隔离客户数据的方法是拥有客户特定的数据库.这对Entity Framework 6非常有效,因为我们需要做的就是将客户特定的连接字符串传递给DbContext所有人,并且一切都运行良好.

由于与此问题无关的原因,我们需要从每个客户模型中移除这一个数据库.从数据隔离的角度来看,每个客户拥有一个数据库架构而不是每个客户一个数据库似乎是一个好主意.在做了一些测试之后,当我们讨论大量不同的模式时,它似乎几乎无法使用.

以下是我们目前使用方式的简化示例DbContext:

public class CustomDbContext : DbContext

    public CustomDbContext(IConnectionStringProvider provider)
        : base(provider.ConnectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new SomeEntityMap());
        modelBuilder.Configurations.Add(new SomeOtherEntityMap());
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是我们认为它可以如何工作的示例:

public class CustomDbContext : DbContext, IDbModelCacheKeyProvider

    public CustomDbContext(IConnectionStringProvider provider)
        : base(provider.ConnectionString)
    {
        CacheKey = provider.Schema;
    }

    public string CacheKey { get; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema(CacheKey);
        modelBuilder.Configurations.Add(new SomeEntityMap());
        modelBuilder.Configurations.Add(new SomeOtherEntityMap());
    }
}
Run Code Online (Sandbox Code Playgroud)

微软已经足够允许一种绕过数据库模型的默认缓存的方法.使用模式名称作为缓存键可强制Entity Framework为每个模式创建新模型.理论上这是有效的.在实践中,不是真的.我创建了一个测试应用程序,它向一个导致DbContext实例化的服务发出请求.它CacheKey从一组5000个密钥中随机化,因此基本上当应用程序首次启动时,几乎每个请求都会OnModelCreating()被调用.在几百个请求之后,IIS工作进程已经占用了所有可用内存(使用大约9 GB),CPU使用率接近100%,服务几乎停滞不前.

我查看了Entity Framework的源代码,并希望使用带有模型构建器的空字符串HasDefaultSchema()会使EF使用数据库用户的默认模式.然后,我们可以通过为每个客户的数据库凭据设置默认架构来缓存一个模型并使模式"在连接字符串中定义".但是,如果架构是空字符串,则EF会抛出异常.

所以问题是,有没有人偶然发现同样的问题,如果是这样,你是如何解决的?如果解决方案只是分叉实体框架,我将不胜感激任何有关所需更改的广泛性的见解.

gel*_*upa 1

感谢伊万·斯托耶夫为我指明了正确的方向。拦截器绝对是解决这个问题的最简单方法。经过 1000 个连续请求的测试,使用拦截器时对执行时间没有明显影响。如果没有额外的工作,此方法将无法与 EF 迁移一起使用,但由于我们不使用它,所以这不是问题。

编辑:对示例进行了一些修复

这是一个似乎可以解决问题的示例:

public class CustomDbContext : DbContext
{
    static CustomDbContext()
    {
        Database.SetInitializer<CustomDbContext>(null);
        DbInterception.Add(new SchemaInterceptor());
    }

    public CustomDbContext(IConnectionStringProvider provider)
        : base(provider.ConnectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.HasDefaultSchema("RemoveThisDefaultSchema");
        modelBuilder.Configurations.Add(new SomeEntityMap());
        modelBuilder.Configurations.Add(new SomeOtherEntityMap());
    }
}

public class SchemaInterceptor : IDbCommandInterceptor
{
    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
    }

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        command.CommandText = command.CommandText.Replace("[RemoveThisDefaultSchema].", string.Empty);
    }

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        command.CommandText = command.CommandText.Replace("[RemoveThisDefaultSchema].", string.Empty);
    }

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
    }

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        command.CommandText = command.CommandText.Replace("[RemoveThisDefaultSchema].", string.Empty);
    }
}
Run Code Online (Sandbox Code Playgroud)