Выполнение проекций для нескольких баз данных только с одним dbContext
Мы используем EF.Core для текущего проекта. У проекта есть три базы данных, что является настоящей неприятностью, которую нельзя избежать. Базы данных имеют одинаковую структуру. Таким образом, мы можем менять контексты и использовать одну и ту же модель EF для выполнения операций CRUD.
У нас есть один особенно сложный запрос, который мы собираемся перенести из традиционного необработанного SQL в ADO.NET. Проблема в том, что это кросс-запрос к базе данных. Чтобы повторить эту проблему, нам нужен способ получить все три таблицы для рассматриваемого запроса из трех баз данных в одном и том же dbContext.
Абсолютный кошмар, чтобы начать, перепробовал много вещей. Работал с таблицей в иерархии (TPH) для наследования в EF Core. Я не верю, что это может быть сделано в отношении нескольких баз данных. Вот полезный сайт, который мы использовали. http://learnentityframeworkcore.com/inheritance
Таким образом, мы задавались вопросом, может ли кто-либо иметь приличную информацию об этом, может быть достигнуто с помощью EF
2 ответа
Итак, я считаю, что мы нашли способ выполнять кросс-запросы в базе данных, используя EF Core, с выполнением в базе данных, а не в разделяемой памяти. Так что я сделаю это с системой запасов в качестве примера. Надеюсь, это кому-нибудь пригодится.
Прежде всего необходимо создать синонимы для базы данных SQLSERVER в основной таблице, чтобы все связанные таблицы были связаны друг с другом.
Тогда наши EF-модели и производные классы должны быть следующими.
public abstract class Stock
{
public int ID{get;set;}
public string stockName{get;set;}
}
[Table("dbname.scheme.Stock", Schema = "dbo")]
public class WinterStock : Stock
{
public WinterStock() : base() { }
}
[Table("dbname.scheme.Stock", Schema = "dbo")]
public class SummerStock : Stock
{
public SummerStock() : base() { }
}
В нашем методе испытаний мы имеем...
var query = _context.Set<WinterStock>().Join(_context.Set<SummerStock>(), winterStock => winterStock.stockName,summerStock => summerStock.stockName, (summerStock,winterStock) => new { summerStock, winterStock });
var result = query.ToList();
Таким образом, спрашивающий (P.Joe) отвечает на свой вопрос здесь ( /questions/44219329/vyipolnenie-proektsij-dlya-neskolkih-baz-dannyih-tolko-s-odnim-dbcontext/44219337#44219337), но я не могу заставить этот ответ работать. Причина, по которой это не работает
[Table("dbname.scheme.Stock", Schema = "dbo")]
будет отображаться в SQL как
[dbo].[dbname.scheme.Stock]
когда то, что вы действительно хотите в SQL
[dbname].[dbo].[Stock]
Даже если вы попытаетесь возиться с этим, это не сработает. Я пробовал разные комбинации
[Table("Stock", Schema = "dbname.dbo")] // SQL => [dbname.dbo].[Stock]
[Table("Stock", Schema = "[dbname].[dbo]")] // SQL => [[dbname]].[dbo]]].[Stock]
[Table("Stock", Schema = "dbname].[dbo")] // SQL => [dbname]].[dbo].[Stock]
[Table("Stock", Schema = "dbname.[dbo")] // SQL => [dbname.[dbo].[Stock]
...так далее. Основная проблема заключается в том, что вам нужно разделить имя базы данных скобками, которые экранируются при его анализе.
На самом деле это, кажется, известная проблема, решение которой находится на стадии разработки (хотя она еще не была расставлена по приоритетам):
https://github.com/aspnet/EntityFrameworkCore/issues/4019
Однако я нашел временное решение этой проблемы, и оно основано на двух источниках:
/questions/33696621/kross-zaprosyi-k-baze-dannyih-v-ef/33696650#33696650 (решение EF6) https://weblogs.asp.net/ricardoperes/interception-in-entity-framework-core
И вот оно:
Как сделать (тот же сервер) Cross DB объединяется с одним EF Core DbContext
Вам нужно будет установить пакет Nuget Microsoft.Extensions.DiagnosticAdapter
using System;
using System.Data.Common;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DiagnosticAdapter;
namespace Example
{
public class CommandInterceptor
{
[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
{
var secondaryDatabaseName = "MyOtherDatabase";
var schemaName = "dbo";
var tableName = "Stock";
command.CommandText = command.CommandText.Replace($" [{tableName}]", $" [{schemaName}].[{tableName}]")
.Replace($" [{schemaName}].[{tableName}]", $" [{secondaryDatabaseName}].[{schemaName}].[{tableName}]");
}
}
}
Замените "MyOtherDatabase", "dbo" и "Stock" на имя вашей базы данных, схему таблицы и имя таблицы, возможно, из конфигурации и т. Д.
Затем прикрепите этот перехватчик к вашему контексту.
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
var context = new MultipleDatabasesExampleDbContext(optionsBuilder.Options);
// Add interceptor to switch between databases
var listener = context.GetService<DiagnosticSource>();
(listener as DiagnosticListener).SubscribeWithAdapter(new CommandInterceptor());
В моем случае я положил выше в метод MultipleDatabasesExampleDbContextFactory.
Теперь вы можете просто использовать контекст, как если бы вы ссылались на одну базу данных.
context.Customers // Default database defined in connection string
context.Stocks // MyOtherDatabase (a different database on the same server)
Интересная проблема.
Один контекст не может быть в нескольких базах данных,
использование нескольких контекстов приведет к загрузке данных в память и выполнению linq для объектов, что, возможно, нежелательно или даже невозможно.
Первый вариант - использовать связанные таблицы в базе данных и запрашивать их. который я считаю самым простым.
Я не знаю, насколько сложен ваш запрос и насколько велики данные, чтобы предложить сценарий в памяти. На основании того, что вы делаете в своем запросе, у вас могут быть специальные решения, примеры:
- Вы можете реализовать некоторые перечислители, которые загружают данные из одной базы данных в определенном порядке, необходимом для эффективного процесса памяти, затем вы можете объединить их в один и использовать его.
- если ваш запрос больше касается объединений больших данных, вы должны разбить его, создать объекты с минимальным количеством столбцов, проецировать на них и реализовать запрос на его основе, в дальнейшем вы можете использовать результаты этого запроса в других запросах, которые будут загружать отсутствующие колонны.