实体框架不包括插入查询中具有默认值的列
Posted
技术标签:
【中文标题】实体框架不包括插入查询中具有默认值的列【英文标题】:Entity Framework not including columns with default value in insert into query 【发布时间】:2017-03-29 21:28:55 【问题描述】:我有一个模型,它有一些用默认值定义的列,例如
table.Column<bool>(nullable: false, defaultValueSql: "1")
当我使用context.SaveChanges()
在数据库中保存一个新实体时,我注意到具有默认值的列不包含在 Entity Framework 生成的插入查询中,因此数据库中生成的值是默认值我在模型中传递的那些。
我是否必须在上下文中设置一些属性才能通过代码设置这些属性?我正在使用 EF Core,但我不知道这是否是所有 EF 版本的一般行为。
更新:代码非常简单。这是我所拥有的伪代码。
该模型有一些用约束定义的属性,例如我上面描述的那个
table.Column<bool>(nullable: false, defaultValueSql: "1")
我将使用列 MyBooleanProperty 作为示例。我有一个服务:
var newModel = new GEAddress();
newModel = someEntity.MyBooleanProperty; //it is false,but ends up as 1 in the database
我正在使用工作单元和存储库,所以我有
_unitOfWork.MyModelRepository.Add(newModel);
_unitOfWork.SaveChanges();
在 VS 的输出窗口中,我看到它如何在 INSERT INTO
查询中发送所有属性,然后它对具有默认值的属性执行 SELECT
。结果是数据库中的 newModel ,其中包含我发送的所有值,但具有默认值的列除外。
我无法更改这些表的配置,因为它正被另一个需要这些规则的系统使用。
我想知道为什么会发生这种情况而不是解决方案。我可以解决这个问题,但我想知道为什么会发生这种行为
【问题讨论】:
一般行为是“列的默认值是插入新行但未为列指定值时将插入的值。”在您的情况下,您似乎正在丢失设置的值。你能发布你的代码吗? FWIW,EF6 不支持默认值 - 您必须为所有列显式指定值,即使非NULL
列具有显式默认值集。
在这种情况下,您可以删除列的配置以禁用默认值行为,这样默认值将由数据库提供。你能展示你的代码(DbContext、Mappings、Pocos)吗?
请说明您是如何映射该属性的。如果在插入后查询,则必须映射为DatabaseGeneratedOption.Computed
。
哪个 EF Core 版本?
【参考方案1】:
我会称之为错误。
我整理了一个简单的测试,只是一个带有一些默认值的类:
public class Test
public int ID get; set;
public int IntDef get; set;
public bool BoolDef get; set;
public DateTime? DateDef get; set;
(注意 DateTime 可以为空)
映射:
modelBuilder.Entity<Test>().HasKey(a => a.ID);
modelBuilder.Entity<Test>().Property(s => s.DateDef).HasDefaultValueSql("GETDATE()");
modelBuilder.Entity<Test>().Property(s => s.IntDef).HasDefaultValueSql("1");
modelBuilder.Entity<Test>().Property(s => s.BoolDef).HasDefaultValue(true);
// Equivalent:
// modelBuilder.Entity<Test>().Property(s => s.BoolDef).HasDefaultValueSql("1");
创建表的 SQL 语句:
CREATE TABLE [Tests] (
[ID] int NOT NULL IDENTITY,
[BoolDef] bit NOT NULL DEFAULT 1,
[DateDef] datetime2 DEFAULT (GETDATE()),
[IntDef] int NOT NULL DEFAULT (1),
CONSTRAINT [PK_Tests] PRIMARY KEY ([ID])
);
当我插入一个新的Test
而不设置任何值时,插入语句是:
INSERT INTO [Tests]
DEFAULT VALUES;
SELECT [ID], [BoolDef], [DateDef], [IntDef]
FROM [Tests]
WHERE @@ROWCOUNT = 1 AND [ID] = scope_identity();
您看到三个默认值(以及生成的标识值)是在插入之后从数据库中读取的。 [顺便说一下,这是 EF-Core 中的新功能。在 EF6 中,只有标记为 DatabaseGeneratedOption.Computed
的标识值和列值在插入(和更新)后从数据库中读取]。
这是创建的Test
对象:
ID IntDef BoolDef DateDef
1 1 True 21-11-16 19:52:56
现在我插入一个新的Test
并分配所有值,但为了好玩,我对不可为空的类型使用默认值:
var item = new Test
IntDef = default(int), // 0
BoolDef = default(bool), // false
DateDef = default(DateTime), // 01-01-01 0:00:00
;
这是 SQL 语句:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Tests] ([DateDef])
VALUES (@p0);
SELECT [ID], [BoolDef], [IntDef]
FROM [Tests]
WHERE @@ROWCOUNT = 1 AND [ID] = scope_identity();
',N'@p0 datetime2(7)',@p0='0001-01-01 00:00:00'
当然,EF 无法推断默认值是故意分配的。如您所见,仅对于可为空的DateTime
列插入分配的值,而不是为不可为空的列。现在DateDef
的值在插入后不会从数据库中读取。
实体值是:
ID IntDef BoolDef DateDef
1 1 True 01-01-01 0:00:00
在保存实体后不是人们所期望的——根本不是!
这意味着:
当您在 EF-Core 中配置具有默认值的属性,并且此默认值不同于 .Net 默认值时,您无法插入具有 .Net 类型默认值的实体 (如 false
用于布尔值)。
我认为这是一个严重的错误,甚至可能会取消新的 EF-Core 行为关于默认值的资格。
加法
正如 Ivan 的评论中所说,您可以通过添加 ValueGeneratedNever()
来阻止 EF 为您设置默认值,例如:
modelBuilder.Entity<Test>().Property(s => s.IntDef)
.HasDefaultValueSql("1").ValueGeneratedNever();
现在该值将按原样保存,EF 不会在插入和更新后将其读回。总而言之,我认为为不可为空的属性定义默认值是没有用的。
使用 EF Core 1.1.0 预览版测试。
【讨论】:
我会将其标记为答案,因为它确认该行为确实存在问题。谢谢! 只是补充一点,目前没有办法在运行时动态控制该行为。您所能做的就是使用ValueGeneratedNever()
静态关闭它。
谢谢@Ivan,还不知道这个。我认为我们可以为 SO 提出的关于这种行为的大量问题做好准备。
我在 github 上发布了这个问题,看来您必须将属性定义为可为空,以便 CLR 知道何时分配您传递的值。我没有测试所有案例,但这里是链接github.com/aspnet/EntityFramework/issues/7089
@monica 我建议您提交增强请求。例如,为新对象设置PropertyEntry.IsModified
为true
,或者考虑属性/字段等的DefaultValue
属性。【参考方案2】:
如果您没有使用正确的属性将属性标记为计算
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
ef 将生成带有属性值的插入语句。 在更新期间,EF 生成 UPDATE 语句 SET 子句,只插入更改的值。
无论如何,您已经有一个解决方法,如果该属性仅由 DBMS 生成,您可以使用上面的属性,否则您必须在表示实体的类的构造函数中插入默认值。
【讨论】:
DatabaseGeneratedOption.Computed
表示其值将始终由数据库提供的列,导致 EF 忽略用户代码对该列的更新。 OP 只想提供一个默认值,以便在未指定值时使用,但能够在插入或修改时指定它。
我同意@bubi,我还有其他具有该属性的列,它们工作正常。我的问题是那些没有得到插入值的默认值
@CodeCaster 问题是 EF 的概念 not specified 不存在(存在被跟踪实体的概念 changed)所以如果你不要指定Computed
EF 将始终生成插入语句。【参考方案3】:
ValueGeneratedNever
没有为我完成这项工作(Entity Framework Core 3.1.21)。我只是从 DBContext 中删除了.HasDefaultValueSql
,并在数据库模式中保留了默认值。工作正常,也许这个笔记对某人有用
【讨论】:
以上是关于实体框架不包括插入查询中具有默认值的列的主要内容,如果未能解决你的问题,请参考以下文章