Я новичок в EF (моя первая неделя), но не новичок в базах данных или программировании. Другие задавали аналогичные вопросы, но я не чувствую, что его спрашивают с правильной детализацией или объясняют так, как это нужно объяснять, поэтому я иду.
Вопрос: Как я могу заставить Entity Framework правильно обрабатывать столбцы в базе данных, которые имеют DEFAULT CONSTRAINT, определенные при выполнении INSERT? Смысл, если я не поставлю значение в моей модели во время операции вставки, как мне получить EF, чтобы исключить этот столбец из его сгенерированной команды TSQL INSERT, чтобы работать с DEFAULT CONSTRAINT, определенным в базе данных?
Фон
У меня есть простая таблица, которую я создал, просто для проверки Entity Framework 6 (EF6) и ее взаимодействия с столбцами, которые SQL Server способен обновлять. Это использует IDENTITY, TIMESTAMP, COMPUTED и несколько столбцов с примененным ПО DEFAULT CONSTRAINT.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DBUpdateTest](
[RowID] [int] IDENTITY(200,1) NOT NULL,
[UserValue] [int] NOT NULL,
[DefValue1] [int] NOT NULL,
[DefValue2null] [int] NULL,
[DefSecond] [int] NOT NULL,
[CalcValue] AS
(((([rowid]+[uservalue])+[defvalue1])+[defvalue2null])*[defsecond]),
[RowTimestamp] [timestamp] NULL,
CONSTRAINT [PK_DBUpdateTest] PRIMARY KEY CLUSTERED
(
[RowID] ASC
)
WITH
(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
ALTER TABLE [dbo].[DBUpdateTest]
ADD CONSTRAINT [DF_DBUpdateTest_DefValue1]
DEFAULT ((200)) FOR [DefValue1]
GO
ALTER TABLE [dbo].[DBUpdateTest]
ADD CONSTRAINT [DF_DBUpdateTest_DefValue2null]
DEFAULT ((30)) FOR [DefValue2null]
GO
ALTER TABLE [dbo].[DBUpdateTest]
ADD CONSTRAINT [DF_DBUpdateTest_DefSecond]
DEFAULT (datepart(second,getdate())) FOR [DefSecond]
GO
EF6 отлично обрабатывает столбцы IDENTITY, TIMESTAMP и COMPUTED, что означает после INSERT или UPDATE (через context.SaveChanges()
). EF считывает новые значения обратно в объект сущности для немедленного использования.
Однако этого не происходит для столбцов с DEFAULT CONSTRAINT. И из того, что я могу сказать, это связано с тем, что, когда EF генерирует TSQL для выполнения INSERT, он предоставляет общее значение по умолчанию для типов с нулевым или непустым значением, точно так же, как если бы этот столбец не имел на нем DEFAULT CONSTRAINT. Таким образом, кажется очевидным, что EF полностью игнорирует возможность DEFAULT CONSTRAINT.
Вот мой EF-код для INSERT записи DBUpdateTest (и я только обновляю один столбец):
DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();
Вот созданный EF SQL во время INSERT для DBUpdateTest (который покорно обновляет все возможные столбцы):
exec sp_executesql
N'INSERT [dbo].[DBUpdateTest]([UserValue], [DefValue1], [DefValue2null],
[DefSecond])
VALUES (@0, @1, NULL, @2)
SELECT [RowID], [CalcValue], [RowTimestamp]
FROM [dbo].[DBUpdateTest]
WHERE @@ROWCOUNT > 0 AND [RowID] = scope_identity()',
N'@0 int,@1 int,@2 int',@0=86,@1=0,@2=54
Обратите внимание, что это очень ясно указывает, что будет значением по умолчанию для INT NOT NULL (0) и INT NULL (null), которое полностью преодолевает DEFAULT CONSTRAINT.
Это то, что происходит, когда выполняется команда EF INSERT, где она снабжает NULL для столбца с нулевым значением и ZERO для столбца INT
RowID UserValue DefValue1 DefValue2null DefSecond CalcValue
=========================================================================
211 100 200 NULL 0 NULL
Если, с другой стороны, я выполняю это утверждение:
insert into DBUpdateTest (UserValue) values (100)
Я получу такую запись
RowID UserValue DefValue1 DefValue2null DefSecond CalcValue
=========================================================================
211 100 200 30 7 3787
Это работает как ожидалось по одной причине: команда TSQL INSERT не предоставляла значения для любых столбцов с определенным ПО УМОЛЧАНИЕМ.
То, что я пытаюсь сделать, заключается в том, чтобы заставить EF исключить столбцы DEFAULT CONSTRAINT из TSS INSERT, если я не устанавливаю для них значения в объекте модели.
Вещи, которые я уже пробовал
1. Признать ограничение по умолчанию? SO: Как заставить EF обрабатывать ограничение по умолчанию
В методе OnModelCreating()
моего класса DbContext
было рекомендовано, чтобы я мог сказать EF, что столбец с DEFAULT CONSTRAINT является полем COMPUTED, а это не так. Тем не менее, я хотел бы узнать, получит ли EF хотя бы считывание значения после INSERT (неважно, что это также, вероятно, не позволит мне назначить значение этому столбцу, что является более чем то, что я делаю не хочу):
modelBuilder.Entity<DBUpdateTest>()
.Property(e => e.DefValue1)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
Это не работает, и на самом деле он вообще ничего не делает (ED: на самом деле это работает, см. пункт 2). EF по-прежнему генерирует один и тот же TSQL, предоставляя значения по умолчанию для столбцов и разбивая базу данных в процессе.
Есть ли флаг, который мне не хватает, элемент конфигурации, который я забываю установить, атрибут функции, который я могу использовать, некоторый код унаследованного класса, который я могу создать, чтобы заставить EF "правильно обрабатывать столбцы DEFAULT CONSTRAINT?"
2. Получить OnModelCreating() для выполнения? SO: OnModelCreating не вызывается
Janesh (внизу) показал мне, что EF будет удалять параметры из его сгенерированной команды TSQL INSERT, если столбец отмечен DatabaseGeneratedOption.Computed
. Это просто не работало для меня, потому что, видимо, я использовал неправильную строку соединения (!!!).
Вот мой App.config, и вот раздел <connectionStrings>
, где я показываю строку "bad" и "good" connect:
<connectionStrings>
<add name="TEST_EF6Entities_NO_WORKY" providerName="System.Data.EntityClient" connectionString="metadata=res://*/TCXModel.csdl|res://*/TCXModel.ssdl|res://*/TCXModel.msl;provider=System.Data.SqlClient;provider connection string="data source=...ConnectStringHere...;App=EntityFramework"" />
<add name="TEST_EF6Entities_IT_WORKS" providerName="System.Data.SqlClient" connectionString="data source=...ConnectStringHere...;App=EntityFramework;" />
</connectionStrings>
Разница. В той, которая работает, используется ProviderName System.Data.SqlClient
, то, которая не работает, использует System.Data.EntityClient
. По-видимому, поставщик SqlClient позволяет вызвать метод OnModelCreating()
, что позволяет использовать эффект DatabaseGeneratedOption.Computed
.
========== НЕ РЕШЕНО ==========
Цель DEFAULT CONSTRAINTS в столбцах - позволить мне предоставлять (или не поставлять) значение и все же в конечном итоге иметь действительное значение на стороне базы данных. Мне не нужно знать, что SQL Server делает это, и я не должен знать, что такое значение по умолчанию или должно быть. Это происходит полностью вне моего контроля или знаний.
Точка, У меня есть опция, не поставляющая значение. Я могу предоставить его, или я не могу его предоставить, и я могу сделать это по-разному для каждого INSERT, если это необходимо.
Использование DatabaseGeneratedOption.Computed
действительно не является допустимым вариантом для этого случая, потому что он заставляет выбрать: "вы можете ВСЕГДА предоставить значение (и, следовательно, НИКОГДА не использовать механизм по умолчанию базы данных), или вы НИКОГДА не можете обеспечить значение (и, следовательно, ВСЕГДА используйте механизм базы данных по умолчанию)".
Кроме того, этот параметр явно предназначен для использования только на фактических вычисленных столбцах, а не для столбцов с DEFAULT CONSTRAINT, потому что после применения свойство модели эффективно становится READ-ONLY для целей INSERT и UPDATE - поскольку это как реальная вычислительная колонка будет работать. Очевидно, это мешает моему выбору поставлять или не предоставлять значение для базы данных.
Итак, я все еще спрашиваю: как я могу заставить EF работать "правильно" с столбцами базы данных, которые определены с помощью DEFAULT CONSTRAINT?