我对EF还不熟悉(这是我的第一周),但我对数据库和编程并不陌生。其他人也问过类似的问题,但我认为他们没有提供足够详细的信息或者解释得不够清楚,所以我来尝试一下。
问题: 当我执行INSERT操作时,如何让Entity Framework正确处理具有DEFAULT CONSTRAINT约束的数据库列?也就是说,如果我在模型中没有提供值,我如何让EF排除该列,从而使数据库定义的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的可能性。
以下是我用于插入DBUpdateTest记录的EF代码(我只更新了单个列):
DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();
以下是在向DBUpdateTest进行插入时EF生成的SQL(它会更新所有可能的列):
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,而INT列将被提供ZERO。
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 命令没有为任何具有定义的 DEFAULT CONSTRAINT 的列提供值。
因此,我想做的是,如果在模型对象中未显式设置这些列的值,则让 EF 排除 INSERT TSQL 中的 DEFAULT CONSTRAINT 列。
我已经尝试过的事情
1. 认识默认约束? SO:如何让 EF 处理默认约束
在我的 DbContext
类的 OnModelCreating()
方法中,建议我告诉 EF 具有 DEFAULT CONSTRAINT 的列是计算字段,但实际上它不是。但是,我想看看是否能让 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>
部分:<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>
区别:有效的使用了 System.Data.SqlClient 的 ProviderName,无效的使用了
System.Data.EntityClient
。显然,SqlClient提供程序允许调用 OnModelCreating()
方法,这使得我可以使用 DatabaseGeneratedOption.Computed
生效。
========== 未解决 ==========
列上默认约束的目的是允许我提供(或不提供)值,并仍在数据库端获得有效值。我不必知道SQL Server正在执行此操作,也不必知道默认值是什么或应该是什么。这完全发生在我的控制或知识之外。关键是,我有选择的权利不提供值。我可以提供它,也可以不提供它,并且如果需要,我可以为每个INSERT提供不同的值。
使用
DatabaseGeneratedOption.Computed
对于这种情况真的不是一个有效的选择,因为它强制要求做出选择:“您始终可以提供一个值(因此永远不会利用数据库默认机制),或者永远不提供一个值(因此总是利用数据库默认机制)”。此外,该选项显然仅用于实际的计算列,并且不适用于具有DEFAULT CONSTRAINTs的列,因为一旦应用,则模型属性对于INSERT和UPDATE而言实际上变为只读-因为这就是真正的计算列的工作方式。显然,这妨碍了我选择向数据库提供或不提供值的权利。那么,我仍然想问:如何让EF“正确”地处理具有DEFAULT CONSTRAINT定义的数据库列?