Entity Framework и SQL Server View

132

По нескольким причинам, о которых я не имею права говорить, мы определяем представление о нашей базе данных Sql Server 2005 следующим образом:

CREATE VIEW [dbo].[MeterProvingStatisticsPoint]
AS
SELECT
    CAST(0 AS BIGINT) AS 'RowNumber',
    CAST(0 AS BIGINT) AS 'ProverTicketId',
    CAST(0 AS INT) AS 'ReportNumber',
    GETDATE() AS 'CompletedDateTime',
    CAST(1.1 AS float) AS 'MeterFactor',
    CAST(1.1 AS float) AS 'Density',
    CAST(1.1 AS float) AS 'FlowRate',
    CAST(1.1 AS float) AS 'Average',
    CAST(1.1 AS float) AS 'StandardDeviation',
    CAST(1.1 AS float) AS 'MeanPlus2XStandardDeviation',
    CAST(1.1 AS float) AS 'MeanMinus2XStandardDeviation'
WHERE 0 = 1

Идея состоит в том, что Entity Framework создаст объект на основе этого запроса, что он и делает, но генерирует его с ошибкой, которая гласит следующее:

Предупреждение 6002: для таблицы / представления «Keystone_Local.dbo.MeterProvingStatisticsPoint» не определен первичный ключ. Ключ был выведен, и определение было создано как таблица / представление только для чтения.

И он решает, что поле CompletedDateTime будет первичным ключом этой сущности.

Мы используем EdmGen для генерации модели. Есть ли способ, чтобы структура сущностей не включала какое-либо поле этого представления в качестве первичного ключа?

Серхио Ромеро
источник

Ответы:

245

У нас была та же проблема, и это решение:

Чтобы заставить платформу сущностей использовать столбец в качестве первичного ключа, используйте ISNULL.

Чтобы заставить каркас сущностей не использовать столбец в качестве первичного ключа, используйте NULLIF.

Самый простой способ применить это - заключить оператор выбора вашего представления в другой выбор.

Пример:

SELECT
  ISNULL(MyPrimaryID,-999) MyPrimaryID,
  NULLIF(AnotherProperty,'') AnotherProperty
  FROM ( ... ) AS temp
Tillito
источник
2
Я думаю, что это лучшее, на что можно надеяться. Итог это работает.
MvcCmsJon
1
Спасибо! Это сработало отлично. @sabanito Я думаю, что это анализирует определение. Вот почему вам нужно специально обернуть ключевые свойства в IsNull (). У меня есть представление, которое не возвращает никаких нулей (и не может возвращать никаких нулей), но из-за способа, которым была написана логика, EF не мог определить, что это было так, пока я не завернул ключи в IsNull ().
Раввин
3
Единственная проблема, которую я вижу здесь, заключается в том, что представлению может на законных основаниях возвращаться пустая строка ''. То, что я сделал, было просто приведение столбца к его собственному типу данных. например, если у AnotherProperty был тип данных varchar (50), я бы использовал его как «CONVERT (VARCHAR (50), AnotherProperty) AS [AnotherProperty]». это маскировало Nullability от EF и также позволило пустые строки.
Барт
2
да, это работает, например, чтобы EF использовал столбец в качестве первичного ключа isnull (CONVERT (VARCHAR (50), newid ()), '') AS [PK]
dc2009
2
Помимо того, что в решении просто раздражающее сообщение, есть ли вред, если не исправить это? Я согласен с вашим решением, но, честно говоря, я не считаю, что мне нужно это делать - я думаю, что мы все можем согласиться, что это ошибка, верно?
dyslexicanaboko
67

Я смог решить эту проблему с помощью дизайнера.

  1. Откройте Браузер моделей.
  2. Найдите вид на диаграмме.
  3. Щелкните правой кнопкой мыши по первичному ключу и убедитесь, что «Ключ сущности» отмечен.
  4. Multi-выбрать все не первичные ключи. Используйте клавиши Ctrl или Shift.
  5. В окне «Свойства» (при необходимости нажмите клавишу F4), измените раскрывающийся список «Ключ сущности» на «Ложь».
  6. Сохранить изменения.
  7. Закройте Visual Studio и снова откройте его. Я использую Visual Studio 2013 с EF 6, и мне пришлось сделать это, чтобы предупреждения исчезли.

Мне не нужно было менять свое мнение, чтобы использовать обходные пути ISNULL, NULLIF или COALESCE. Если вы обновите свою модель из базы данных, предупреждения снова появятся, но исчезнут, если вы закроете и снова откроете VS. Изменения, сделанные в конструкторе, будут сохранены и не будут затронуты обновлением.

Кейси Пламмер
источник
9
Подтверждено. Необходимо перезагрузить VS2013, чтобы предупреждение исчезло.
Михаил Логутов
5
"Вы пытались выключить и снова включить?" ;-) Спасибо, работает как шарм!
Обл Тобл
4
Когда я создаю виды, они даже не попадают в диаграмму модели. Они прокомментированы в XML-файле
ggderas
Простое и легкое решение, которое не кажется таким уж ненормальным, как манипулирование видом! Спасибо.
LuqJensen
2
Подтверждено, что VS2017 необходимо перезапустить, чтобы предупреждение исчезло.
Марк Левеск
46

Согласитесь с @Tillito, однако в большинстве случаев это приведет к загрязнению оптимизатора SQL и не будет использовать правильные индексы.

Это может быть очевидно для кого-то, но я потратил часы на решение проблем с производительностью, используя решение Tillito. Допустим, у вас есть таблица:

 Create table OrderDetail
    (  
       Id int primary key,
       CustomerId int references Customer(Id),
       Amount decimal default(0)
    );
 Create index ix_customer on OrderDetail(CustomerId);

и ваш взгляд примерно такой

 Create view CustomerView
    As
      Select 
          IsNull(CustomerId, -1) as CustomerId, -- forcing EF to use it as key
          Sum(Amount) as Amount
      From OrderDetail
      Group by CustomerId

Оптимизатор Sql не будет использовать индекс ix_customer и будет выполнять сканирование таблицы по первичному индексу, но если вместо:

Group by CustomerId

ты используешь

Group by IsNull(CustomerId, -1)

это заставит MS SQL (по крайней мере, в 2008 году) включить правильный план в план.

Если

Вал Бахтин
источник
2
Это должен быть комментарий к ответу Тиллито, а не сам ответ, поскольку он не дает решения для вопроса ОП.
Зимданен
6
У этого парня 1 повтор, он еще не может добавить комментарий.
jrcs3
@zimdanen Нет никакого способа, которым вы могли бы уместить всю эту информацию в комментарии, более разумно включить ее в отдельный ответ.
Контанго
2
@Contango: Этот ответ был отредактирован через шесть дней после публикации, и я разместил свой комментарий. Смотрите историю изменений.
Зимданен
9

Этот метод хорошо работает для меня. Я использую ISNULL () для поля первичного ключа и COALESCE (), если поле не должно быть первичным ключом, но также должно иметь ненулевое значение. В этом примере возвращается поле идентификатора с необнуляемым первичным ключом. Другие поля не являются ключами и имеют (Нет) в качестве атрибута Nullable.

SELECT      
ISNULL(P.ID, - 1) AS ID,  
COALESCE (P.PurchaseAgent, U.[User Nickname]) AS PurchaseAgent,  
COALESCE (P.PurchaseAuthority, 0) AS PurchaseAuthority,  
COALESCE (P.AgencyCode, '') AS AgencyCode,  
COALESCE (P.UserID, U.ID) AS UserID,  
COALESCE (P.AssignPOs, 'false') AS AssignPOs,  
COALESCE (P.AuthString, '') AS AuthString,  
COALESCE (P.AssignVendors, 'false') AS AssignVendors 
FROM Users AS U  
INNER JOIN Users AS AU ON U.Login = AU.UserName  
LEFT OUTER JOIN PurchaseAgents AS P ON U.ID = P.UserID

если у вас действительно нет первичного ключа, вы можете подделать его, используя ROW_NUMBER, чтобы сгенерировать псевдоключ, который игнорируется вашим кодом. Например:

SELECT
ROW_NUMBER() OVER(ORDER BY A,B) AS Id,
A, B
FROM SOMETABLE
SpazDude
источник
Да, я закончил изменой NEWID() as id, но это та же идея. И есть законные варианты использования - если у вас есть представление только для чтения, например. Гадкий, ЭФ
ruffin
4

Текущий генератор EDM Entity Framework создаст составной ключ из всех необнуляемых полей в вашем представлении. Чтобы получить контроль над этим, вам нужно изменить представление и столбцы базовой таблицы, установив для столбцов значение NULL, если вы не хотите, чтобы они были частью первичного ключа. Также верно и обратное, как я обнаружил, ключ, сгенерированный EDM, вызывал проблемы с дублированием данных, поэтому мне пришлось определить столбец, который можно обнулять как необнуляемый, чтобы составной ключ в EDM включал этот столбец.

Annagram
источник
У нас та же проблема с выведенным PK, объект возвращает дублированные записи и полностью раздражает. Если вы выполняете Context.Entity.ToList()дубликаты записей, но если вы выполняете SQL-запрос, сгенерированный EF напрямую (полученный с помощью LINQPad), дублирование записей не происходит. Кажется, это проблема сопоставления записей базы данных с возвращаемыми объектными объектами (POCO), поскольку PK выводится с использованием объясненной логики (столбцы, не имеющие значения NULL).
Дэвид Оливан Убието
3

Похоже, это известная проблема с EdmGen: http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/12aaac4d-2be8-44f3-9448-d7c659585945/

RBarryYoung
источник
В этом есть смысл. Итак, есть ли способ определить столбец как не нуль или нуль в представлении, как мы его определяем?
Серхио Ромеро
1
Извините, я уже вышел за рамки своего опыта в Entity Framework. :-)
RBarryYoung
1
Кто-нибудь знает, когда эта проблема будет исправлена? Раздражает необходимость обходить это, когда у вас есть ненулевые столбцы, которые не являются первичными ключами.
живи-люби
3

Чтобы получить представление, мне нужно было показать только один столбец первичного ключа. Я создал второе представление, которое указывало на первое, и использовал NULLIF, чтобы типы могли обнуляться. Это помогло мне заставить EF думать, что в представлении был только один первичный ключ.

Не уверен, поможет ли это вам, хотя я не верю, что EF примет объект без первичного ключа.

Ник Готч
источник
3

Если вы не хотите связываться с тем, что должно быть первичным ключом, я рекомендую:

  1. Включите ROW_NUMBERв свой выбор
  2. Установите его в качестве первичного ключа
  3. Установить все остальные столбцы / элементы как неосновные в модели
Santhos
источник
1

Из-за вышеупомянутых проблем я предпочитаю функции табличных значений.

Если у вас есть это:

CREATE VIEW [dbo].[MyView] AS SELECT A, B FROM dbo.Something

создать это:

CREATE FUNCTION MyFunction() RETURNS TABLE AS RETURN (SELECT * FROM [dbo].[MyView])

Затем вы просто импортируете функцию, а не представление.

луч
источник
2
Как бы вы создали ассоциации между сущностями, идущими с этим подходом? Является ли это возможным?
ggderas 19.09.16