Итак, вот в чем дело - в настоящее время я использую EF Core 3.1 и, скажем, у меня есть объект:

public class Entity
{
    public int Id { get; set; }
    public int AnotherEntityId { get; set; }
    public virtual AnotherEntity AnotherEntity { get; set; }
}

Когда я получаю доступ к DbSet<Entity> Entities обычному способу, я включаю AnotherEntity, например:

_context.Entities.Include(e => e.AnotherEntity)

И это работает. Почему бы и нет? Тогда я иду с:

_context.Entities.FromSqlRaw("SELECT * FROM Entities").Include(e => e.AnotherEntity)

И это тоже работает. Оба возвращают мне одну и ту же коллекцию объектов, объединенных с AnotherEntity. Затем я использую хранимую процедуру, которая состоит из того же запроса SELECT * FROM Entities с именем spGetEntities:

_context.Entities.FromSqlRaw("spGetEntities")

Угадай, что? Это тоже работает. Это дает мне тот же вывод, но без присоединенного AnotherEntity, очевидно. Однако, если я пытаюсь добавить Включить, как это:

_context.Entities.FromSqlRaw("spGetEntities").Include(e => e.AnotherEntity)

Я осознаю:

FromSqlRaw или FromSqlInterpolated был вызван с несложным SQL и с запросом, составляющим по нему. Вы можете позвонить AsEnumerable после метода FromSqlRaw или FromSqlInterpolated для выполнения композиция на стороне клиента.

Хотя выходные данные _context.Entities.FromSqlRaw("SELECT * FROM Entities") и _context.Entities.FromSqlRaw("spGetEntities") идентичны.

Я не смог найти доказательств того, что могу или не могу сделать это с EF Core 3.1, но если бы кто-нибудь мог дать мне хоть какой-то намек на возможность такого подхода, было бы неплохо.

Кроме того, если есть другой способ объединения сущностей с помощью хранимой процедуры, я бы, вероятно, принял его как решение моей проблемы.

20
Gleb 17 Дек 2019 в 23:18

2 ответа

Лучший ответ

Короче, вы не можете этого сделать (по крайней мере, для SqlServer). Объяснение содержится в документации EF Core - Необработанные запросы SQL - Компоновка с помощью LINQ :

Компоновка с помощью LINQ требует, чтобы ваш необработанный SQL-запрос был компонуемым, поскольку EF Core будет обрабатывать предоставленный SQL как подзапрос. SQL-запросы, которые можно составить, начинаются с ключевого слова SELECT. Кроме того, передаваемый SQL не должен содержать символы или параметры, которые недопустимы в подзапросе, например:

  • Завершающая точка с запятой
  • На SQL Server - конечная подсказка уровня запроса (например, OPTION (HASH JOIN))
  • В SQL Server предложение ORDER BY, которое не используется с OFFSET 0 OR TOP 100 PERCENT в предложении SELECT

SQL Server не позволяет составлять вызовы хранимых процедур, поэтому любая попытка применить дополнительные операторы запросов к такому вызову приведет к неверному SQL. Используйте метод AsEnumerable или AsAsyncEnumerable сразу после методов FromSqlRaw или FromSqlInterpolated, чтобы убедиться, что EF Core не пытается создать композицию поверх хранимой процедуры.

Кроме того, поскольку для Include / ThenInclude требуется EF Core IQueryable<>, AsEnumerable / AsAsyncEnumerable и т. Д. Это не вариант. Вам действительно нужен составной SQL, поэтому хранимые процедуры не подходят.

Однако вместо хранимых процедур вы можете использовать табличные функции (TVF) или представления базы данных, потому что они компонуются (select * from TVF(params) или select * from db_view).

6
Ivan Stoev 17 Дек 2019 в 21:15

В моем случае я конвертировал рабочий EF FromSql() с кодом хранимой процедуры 2.1 в 3.1. Вот так:

ctx.Ledger_Accounts.FromSql("AccountSums @from, @until, @administrationId",
                                                            new SqlParameter("from", from),
                                                            new SqlParameter("until", until),
                                                            new SqlParameter("administrationId", administrationId));

Где AccountSums - SP.

Единственное, что мне нужно было сделать, это использовать FromSqlRaw() и добавить IgnoreQueryFilters(), чтобы снова заработало. Вот так:

ctx.Ledger_Accounts.FromSqlRaw("AccountSums @from, @until, @administrationId",
               new SqlParameter("from", from),
               new SqlParameter("until", until),
               new SqlParameter("administrationId", administrationId)).IgnoreQueryFilters();

Это упомянуто в комментариях, но я сначала пропустил это, поэтому включил это здесь.

2
Flores 25 Янв 2020 в 10:20