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

时间:2016-09-02 18:21:48

标签: .net entity-framework asp.net-core entity-framework-core .net-core

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

Prime数据库

 dbo.Database  
        DatabaseId INTEGER  
        ConnectionString VARCHAR(200)  

    dbo.Customer  
        CustomerId BIGINT  
        DatabaseId INTEGER  

客户数据库

dbo.Order  
    CustomerId BIGINT  
    OrderId INT  
    ...  

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

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

如果我要创建新的DbContexts,我应该怎么做呢?我能找到的大多数示例代码都使用Startup.cs中的Dependency Injection来创建单例DbContext

1 个答案:

答案 0 :(得分:5)

这是我提出的问题。它仍然非常粗糙,我非常感谢可能提出的批评。

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

<强> 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);
    }
}

<强> 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();
    }
}

控制器创建新碎片的行动

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

用于查询的控制器操作

[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);
    }
}