Entity Framework и представление SQL Server
по нескольким причинам, о которых у меня нет свободы говорить, мы определяем представление в нашей базе данных 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 для генерации модели. Есть ли способ не включать в Entity framework какое-либо поле этого представления в качестве первичного ключа?
9 ответов
у нас была такая же проблема и это решение:
чтобы заставить entity framework использовать столбец в качестве первичного ключа, используйте ISNULL.
чтобы заставить entity framework не использовать столбец в качестве первичного ключа, используйте NULLIF.
простой способ применить это-обернуть оператор select вашего представления в другой select.
пример:
SELECT
ISNULL(MyPrimaryID,-999) MyPrimaryID,
NULLIF(AnotherProperty,'') AnotherProperty
FROM ( ... ) AS temp
Я смог решить эту проблему с помощью конструктора.
- откройте браузер моделей.
- найдите представление на диаграмме.
- щелкните правой кнопкой мыши на первичном ключе и убедитесь, что установлен флажок "Entity Key".
- Multi-выберите все не-первичные ключи. Используйте клавиши Ctrl или Shift.
- в окне Свойства (нажмите F4, если необходимо, чтобы увидеть его), измените Раскрывающийся список "ключ сущности" в False.
- Сохранить изменения.
- Закрыть Visual Студия и снова открыть его. Я использую Visual Studio 2013 с EF 6 и я должен был сделать это, чтобы убрать предупреждения.
Мне не нужно было менять свое мнение, чтобы использовать обходные пути ISNULL, NULLIF или COALESCE. Если вы обновите свою модель из базы данных, предупреждения появятся снова, но исчезнут, если вы закроете и снова откроете VS. Изменения, внесенные в конструктор, будут сохранены и не будут затронуты обновлением.
согласен с @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 optimizer не будет использовать индекс ix_customer, и он будет выполнять сканирование таблицы по первичному индексу, но если вместо:
Group by CustomerId
вы использовать
Group by IsNull(CustomerId, -1)
это заставит MS SQL (по крайней мере, 2008) включить правый индекс в план.
если
этот метод хорошо работает для меня. Я использую ISNULL () для поля первичного ключа и COALESCE (), если поле не должно быть первичным ключом, но также должно иметь ненулевое значение. Этот пример дает поле ID с ненулевым первичным ключом. Другие поля не являются ключами и имеют (None) в качестве атрибута 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
текущий генератор EDM Entity Framework создаст составной ключ из всех ненулевых полей в вашем представлении. Чтобы получить контроль над этим, вам нужно будет изменить представление и базовые столбцы таблицы, установив для столбцов значение nullable, если вы не хотите, чтобы они были частью первичного ключа. Противоположное также верно, поскольку я столкнулся, сгенерированный ключ EDM вызывал проблемы дублирования данных, поэтому мне пришлось определить столбец nullable как не nullable, чтобы заставить составной ключ в EDM включить этот столбец.
похоже, что это известная проблема с EdmGen: http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/12aaac4d-2be8-44f3-9448-d7c659585945/
чтобы получить представление, мне нужно было только показать один столбец первичного ключа я создал второе представление, которое указывало на первое и использовало NULLIF, чтобы сделать типы nullable. Это сработало для меня, чтобы заставить EF думать, что в представлении был только один первичный ключ.
Не уверен, что это поможет вам, поскольку я не верю, что EF примет объект без первичного ключа.
Я также рекомендую, если вы не хотите возиться с тем, что должно быть первичным ключом, включить ROW_NUMBER в ваш выбор и установить его в качестве первичного ключа и установить все другие столбцы/memebers как неосновные в модели.
из-за вышеупомянутых проблем я предпочитаю функции табличного значения.
Если у вас есть это:
CREATE VIEW [dbo].[MyView] AS SELECT A, B FROM dbo.Something
создать этого:
CREATE FUNCTION MyFunction() RETURNS TABLE AS RETURN (SELECT * FROM [dbo].[MyView])
затем вы просто импортируете функцию, а не представление.