在 Entity Framework 中将数据从一个提供商实时传输到另一个提供商

Live Transfer of data from one provider to another in Entity Framework

如果有人问过这个问题,我深表歉意,我正在努力寻找我想要了解的术语,因为它与 Entity Framework 中的功能冲突。

我想做什么:

我想创建一个应用程序,在设置时允许用户使用 1 个数据库作为“试用”/“启动”数据库,即非生产数据库。这将允许用户试用该应用程序但不会有备份等。这绝不是“生产”数据库。例如,这可能是 SQLite。

当用户准备好后,他们可以单击“转换为生产”(或类似的),并为其指定新数据库的目标 machine/database。这将被视为“生产”环境。这可能类似于 MySQL、SQLServer 或..这些天 EF 连接到的任何其他东西..

问题:

EF 是否支持这种类型的 migration/data 实时传输?它是否需要另一个应用程序,您可以在其中为其配置 EF 源和 EF 目标,然后 运行 通过数据源的 conversion/seeding/population 到另一个数据源的过程?

为什么我在这里问:

我试图搜索与此主题相关的内容,但 transferring/migration 提出了完全不相关的主题,因此非常感谢任何帮助。

根据您的描述,我认为没有任何开箱即用的东西可以支持这一点。您可以将 DbContext 映射到任一数据库,然后从评估 DbContext 中获取和分离实体并将它们附加到生产数据库。

对于相对简单的模式/对象图,这将是相当直接的实现。

ICollection<Customer> customers = new List<Customer>();

using(var context = new AppDbContext(evalConnectionString))
{
   customers = context.Customers.AsNoTracking().ToList();
}

using(var context = new AppDbContext(productionConnectionString))
{ // Assuming an empty database...
    context.Customers.AddRange(customers);
}

虽然对于更复杂的模型,这可能需要一些工作,尤其是在处理现有 lookups/references 之类的东西时。如果您想要移动可能共享对另一个对象的相同引用的对象,您需要在目标 DbContext 中查询现有亲属并在保存“父”实体之前替换它们。

ICollection<Order> orders = new List<Order>();

using(var context = new AppDbContext(evalConnectionString))
{
   orders = context.Orders
       .Include(x => x.Customer)
       .AsNoTracking()
       .ToList();
}

using(var context = new AppDbContext(productionConnectionString))
{ 
    var customerIds = orders.Select(x => x.Customer.CustomerId)
        .Distinct().ToList();
    var existingCustomers = context.Customers
        .Where(x => customerIds.Contains(x.CustomerId))
        .ToList();
    
    foreach(var order in orders)
    {  // Assuming all customers were loaded
       var existingCustomer = existingCustomers.SingleOrDefault(x => x.CustomerId == order.Customer.CustomerId);
       if(existingCustomer != null)
           order.Customer = existingCustomer;
       else
           existingCustomers.Add(order.Customer);

       context.Orders.Add(order);
    }
}

这是一个非常简单的示例,用于概述如何处理您可能插入数据时可能存在或可能不存在于目标 DbContext 中的引用的情况。如果我们跨订单复制并想要处理他们各自的客户,我们首先需要检查是否存在任何跟踪的客户参考并使用该参考以避免插入重复行或引发异常。

通常从一个 DbContext 加载订单和相关引用应确保引用同一客户实体的多个订单将共享同一实体引用。但是,要使用我们可以通过 AsNoTracking() 与新 DbContext 关联的分离实体,对同一记录的分离引用将 而不是 是相同的引用,因此我们需要用关心

例如,同一客户有 2 个订单:

var ordersA = context.Orders.Include(x => x.Customer).ToList();
Assert.AreSame(orders[0].Customer, orders[1].Customer); // Passes


var ordersB = context.Orders.Include(x => x.Customer).AsNoTracking().ToList();
Assert.AreSame(orders[0].Customer, orders[1].Customer); // Fails 

即使在第二个示例中,两者都是针对同一客户的。每个都有一个具有相同 ID 的 Customer 引用,但有 2 个不同的引用,因为 DbContext 不跟踪所使用的引用。分离实体和提高性能等努力的几个“陷阱”之一。使用跟踪引用并不理想,因为这些实体仍然认为它们与另一个 DbContext 相关联。我们可以分离它们,但这意味着深入对象图并分离所有引用。 (可行,但与仅将它们分开加载相比比较混乱)

当可能批量迁移数据(定期处理 DbContext 以避免较大数据量的性能缺陷)或随时间同步数据时,它也会变得复杂。通常建议首先检查目标 DbContext 以查找匹配的记录并使用它们来避免插入重复数据。 (或抛出异常)

如此简单的数据模型,这是相当简单的。对于更复杂的数据,其中有更多数据要传递并且数据之间的关系更多,它会更复杂。对于这些系统,我可能会考虑生成数据库到数据库的迁移,例如从源数据库中的数据为所需的目标数据库创建 INSERT 语句。那里只是按关系顺序插入数据以符合数据约束的问题。 (使用工具或滚动您自己的脚本生成)