.net - 使用 Entity Framework Core 的分片策略

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

我正在使用 Asp.Net Core 和 Entity Framework Core 开发一个新的 REST API。我们将从使用水平数据库分区(分片)的遗留系统移植数据。我试图想出一种在 EF Core 中处理这个问题的好方法。我们之前的分片策略涉及一个中央 Prime 数据库和多个客户数据库。所有查询均包含 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 中的依赖注入(inject)来创建单例 DbContext .

最佳答案

这就是我想出的。它仍然非常粗糙,我非常感谢可能提供的任何批评。

我在 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);
    }
}

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

用于创建新分片的 Controller 操作
[HttpPost]
public IActionResult Shard([FromBody] string connectionString) {
    try {
        _shardContextService.ActivateShard(connectionString);
        return Ok("Shard activated");
    } catch (System.Exception e) {
        return StatusCode(500, e);
    }
}

用于查询的 Controller 操作
[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);
    }
}

关于.net - 使用 Entity Framework Core 的分片策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39298630/

相关文章:

c# - c# 中是否有线程安全和通用的 IList<T>?

c# - .NET (C#) 将消息从自定义控件传递到主应用程序

c# - 如何在 Code First 模型中设置 bool 值的默认值?

c# - MVC 4 后期绑定(bind) DataContext 实体 LINQ 引用

rss - 在 ASP.NET Core 1.0 RC2 中使用 RSS 的正确方法

c# - DataGridView如何实现自动滚动?

.net - Windows Metro 应用程序中的 CollectionViewSource

c# - 使用带有域服务的 ado.net Entity Framework 更新父表

c# - 如何在 url 中将字符串作为参数传递给 asp.net core api Controller

c# - 模型绑定(bind)的复杂类型不能是抽象类型或值类型,并且必须具有无参数构造函数