我正在使用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
。
答案 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);
}
}