При выборке из View часть строк дублируется и отличается от результата, возвращаемого SQL Server. Ошибка не нова и связана с тем, что View не содержит столбцов, позволяющих однозначно идентифицировать каждую строку, либо EF не может самостоятельно распознать такие столбцы. Легко лечится. Итак, обо всем по порядку.
Среда выполнения:
И создадим View со следующим запросом:
Самый надежный вариант - решить проблему на уровне SQL Server, и такой способ есть. Добавим во View следующий столбец:
Среда выполнения:
.NET 4.5Возьмем 2 простейшие таблицы:
Entity Framework 6.1.2 (Database First)
SQL Server 2012
И создадим View со следующим запросом:
CREATE VIEW [dbo].[WrongView]
AS
SELECT P.Name AS ProductName
, O.Name AS OrderName
FROM [Product] AS P
INNER JOIN [Order] AS O ON P.Id = O.ProductId
Допустим, у нас есть один единственный продукт и 3 привязанных к нему заказа, в этом случае наш View вернет что-нибудь подобное:
SELECT * FROM [WrongView]
ProductName OrderNameОднако, если обратиться к этому View посредством EF, результат будет иным:
Product #1 Order #1
Product #1 Order #2
Product #1 Order #3
var allRows = context.WrongView.ToList();
ProductName OrderName
Product #1 Order #1
Product #1 Order #1
Product #1 Order #1
При этом на сервер уйдет абсолютно корректный SQL-запрос, ошибка присутствует в маппинге данных. Для работы с View, EF автоматически назначает один или несколько столбцов ключевыми на уровне EDMX-модели. Если View не содержит подходящего not null столбца, либо EF ошиблась с выбором - возникает вышеописанная проблема с дублированием строк.
Простейший workaround - использование AsNoTracking:
Простейший workaround - использование AsNoTracking:
var allRows = context.WrongView.AsNoTracking().ToList();
Почему отсоединение результата запроса от контекста влияет на маппинг, мне не известно. Этот способ плох уже тем, что его необходимо знать и помнить. Немногим лучше - вариант указать столбец вручную в EDMX-модели (я предпочитаю не хранить в EDMX никаких дополнительных настроек, так как потерять их - проще простого. Как показывает практика, удаление таблицы из EDMX с последующим созданием - единственный надежный способ решить конфликты рассинхронизации EDMX и БД).
Самый надежный вариант - решить проблему на уровне SQL Server, и такой способ есть. Добавим во View следующий столбец:
ISNULL(ROW_NUMBER() OVER (ORDER BY P.Name), -1) AS RowId
Функция ROW_NUMBER() нумерует строки в результирующем наборе, а ISNULL() подскажет EF, что столбец является not null и пригодным в качестве ключевого. Мне хватило этого для решения проблемы. Есть еще более усиленный вариант: остальные not null столбцы оборачиваются в вызов функции NULLIF(), а столбец RowId перемещается на первое место:
SELECT ISNULL(ROW_NUMBER() OVER (ORDER BY P.Name), -1) AS RowId
, NULLIF(P.Name, -1) AS ProductName
, O.Name AS OrderName
Теперь EF не запутается!
Комментариев нет:
Отправить комментарий