实体框架不包括插入查询中具有默认值的列

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&lt;bool&gt;(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.IsModifiedtrue,或者考虑属性/字段等的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,并在数据库模式中保留了默认值。工作正常,也许这个笔记对某人有用

【讨论】:

以上是关于实体框架不包括插入查询中具有默认值的列的主要内容,如果未能解决你的问题,请参考以下文章

将具有默认值的列添加到 SQL Server 中的现有表

禁用实体框架的默认值生成(代码优先)

如何在表中添加具有默认值的列

Impala 添加具有默认值的列

使用 Dapper 插入具有默认值的表

Hibernate / JPA在合并后获取数据库中具有默认值的列的值