使用实体框架核心的分片策略

Hom*_*sey 3 .net entity-framework entity-framework-core .net-core asp.net-core

我正在使用Asp.Net Core和Entity Framework Core开发新的REST API。我们将移植来自使用水平数据库分区(分片)的旧版系统中的数据。我正在尝试一种在EF Core中处理此问题的好方法。我们以前的分片策略涉及一个中央Prime数据库和多个Customer数据库。所有查询都包含一个CustomerId。我们使用CustomerId查询Prime数据库,以确定哪个Customer数据库包含特定客户的数据。数据库模式如下所示:

主数据库

 dbo.Database  
        DatabaseId INTEGER  
        ConnectionString VARCHAR(200)  

    dbo.Customer  
        CustomerId BIGINT  
        DatabaseId INTEGER  
Run Code Online (Sandbox Code Playgroud)

客户数据库

dbo.Order  
    CustomerId BIGINT  
    OrderId INT  
    ...  
Run Code Online (Sandbox Code Playgroud)

用于获取订单的REST调用示例类似于 http://foo.com/api/Customers/{CustomerId}/Orders/{OrderId}

我需要在CustomerDbContext每个REST请求中使用动态确定的连接字符串。是否应DbContext为每个请求创建新的实例?或者,我可以在运行时更改连接字符串吗?

如果要创建新的DbContext,该如何处理?我可以找到的大多数示例代码都使用Startup.cs中的依赖注入来创建单例DbContext

Hom*_*sey 5

这是我想出的。它仍然很粗糙,我非常感谢您提出的任何批评。

我向dbo.Database添加了“ UseForNewCustomer BOOLEAN”。我正在使用数据库迁移来动态创建新的碎片。

ShardDbContextFactory

public class ShardDbContextFactory : IDbContextFactory<ShardDbContext>
{
    public ShardDbContext Create(DbContextFactoryOptions opts)
    {
        return this.Create("This-Connection-String-Isn't-Used");
    }

    public ShardDbContext Create(string connectionString)
    {
        var optsBldr = new DbContextOptionsBuilder<ShardDbContext>();
        //This is for PostGres. If using MS Sql Server, use 'UseSqlServer()'
        optsBldr.UseNpgsql(connectionString); 
        return new ShardDbContext(optsBldr.Options);
    }
}
Run Code Online (Sandbox Code Playgroud)

ShardContextService.cs

public interface IShardContextService {
    ShardDbContext GetContextForCustomer(int customerId);
    void ActivateShard(string connectionString, string dbType);
}

public class ShardContextService : IShardContextService {
    private readonly PrimeDbContext _primeContext;
    public ShardContextService(SystemDbContext primeContext) {
        _primeContext = primeContext;
    }

    public CustomerDbContext GetContextForCustomer(int customerId)
    {
        Database shard = null;
        var customer = _primeContext.Customers
            .Include(m=>m.Database)
            .SingleOrDefault(c=>c.CustomerId == customerId);
        if (customer == null)
        {
            shard = _primeContext.Databases.Single(db=>db.UseForNewCustomer);

            if (shard == null) throw new System.Exception("Unable to determine shard: This is a new customer, and no shards are designated as useable for new customers.");

            _primeContext.Customers.Add(new Customer {
                CustomerId = customerId,
                DatabaseId = shard.DatabaseId
            });

            _primeContext.SaveChanges();
        }
        else
        {
            shard = customer.Database;
        }
        return (new ShardDbContextFactory()).Create(shard.ConnectionString)
    }

    public void ActivateShard(string connectionString)
    {
        using (var customerContext = (new ShardDbContextFactory()).Create(connectionString))
        {
            customerContext.Database.Migrate();
        }

        var previous = _primeContext.Databases.SingleOrDefault(d=>d.UseForNewCustomers);
        if (previous != null)
        {
            previous.UseForNewCustomers = false;
        }

        var existing = _primeContext.Databases.SingleOrDefault(d=>d.ConnectionString == connectionString);
        if (existing != null)
        {
            existing.UseForNewCustomers = true;
        }
        else
        {
            _primeContext.Databases.Add(new Database {
                ConnectionString = connectionString,
                UseForNewCustomers = true
            });
        }
        _primeContext.SaveChanges();
    }
}
Run Code Online (Sandbox Code Playgroud)

用于创建新碎片的Controller Action

[HttpPost]
public IActionResult Shard([FromBody] string connectionString) {
    try {
        _shardContextService.ActivateShard(connectionString);
        return Ok("Shard activated");
    } catch (System.Exception e) {
        return StatusCode(500, e);
    }
}
Run Code Online (Sandbox Code Playgroud)

用于查询的控制器操作

[HttpGet]
[Route("/api/Customers/{customerId}/Orders/{orderId}")]
public virtual IActionResult GetOrdersForCustomer([FromRoute]long customerId, [FromRoute] long orderId)
{
    using (var ctx = _shardContextService.GetContextForCustomer(customerId))
    {
        var order = ctx.Orders.Where(o => o.CustomerId == customerId && o.OrderId = orderId).Single();
        if (order == null) return NotFound("Unable to find this order.");
        else return new ObjectResult(order);
    }
}
Run Code Online (Sandbox Code Playgroud)