如何在INSERT期间让EF 6在数据库上处理DEFAULT CONSTRAINT

我是EF的新手(这是我的第一周),但对数据库或编程并不陌生。 其他人也提出了类似的问题,但我觉得它没有被问到正确的细节,或者解释得非常需要解释,所以我走了。

问题:如何让Entity Framework正确处理执行INSERT时定义了DEFAULT CONSTRAINT的数据库中的列? 这意味着,如果我在插入操作期间没有在模型中提供值,如何让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的可能性。

这是我的插入DBUpdateTest记录的EF代码(我只更新一个列):

 DBUpdateTest myVal = new DBUpdateTest(); myVal.UserValue = RND.Next(20, 90); DB.DBUpdateTests.Add(myVal); DB.SaveChanges(); 

这是在INSERT到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的列是COMPUTED字段,而不是。 但是,我想知道在INSERT之后是否会让EF至少读回值(更别提它也可能使我无法为该列分配值,这只是我做的更多)不想):

  modelBuilder.Entity() .Property(e => e.DefValue1) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); 

这不起作用,事实上似乎没有任何不同( ED:实际上它确实有效,见第2点 )。 EF仍然生成相同的TSQL,为列提供默认值并在此过程中破坏数据库。

是否有我缺少的标志,我忘记设置的配置项,我可以使用的函数属性,我可以创建一些inheritance的类代码,以使EF“正确处理DEFAULT CONSTRAINT列?”

2.获取OnModelCreating()来执行? SO:OnModelCreating未调用

Janesh(下面)向我展示如果列标记为DatabaseGeneratedOption.Computed ,EF将从其生成的TSQL INSERT命令中删除参数。 它只是不适合我,因为显然我使用了错误的连接字符串(!!!)。

这是我的App.config,这里是部分,其中我显示了“坏”和“好”的连接字符串:

     

区别:工作的那个使用System.Data.SqlClient ProviderName ,不工作的那个使用System.Data.EntityClient 。 显然,SqlClient提供程序允许OnModelCreating()方法,这允许我使用DatabaseGeneratedOption.Computed来产生效果。

==========没有解决==========

列上的DEFAULT CONSTRAINTS的目的是允许我提供(或不提供)值,并且最终仍然在数据库端获得有效值。 我不必知道SQL Server正在这样做,也不必知道默认值是什么或应该是什么。 这完全超出了我的控制或知识。

关键是, 我可以选择不提供价值。 我可以提供它,或者我可能无法提供它,如果需要,我可以为每个INSERT做不同的事情。

使用DatabaseGeneratedOption.Computed对于这种情况实际上不是一个有效的选项,因为它强制选择:“你总是可以提供一个值(因此永远不会利用数据库默认机制),或者你永远不能提供一个值(因此总是使用数据库默认机制)“。

此外,该选项显然仅用于实际的计算列,而不是用于具有DEFAULT CONSTRAINT的列,因为一旦应用,模型属性实际上变为READ-ONLY用于INSERT和UPDATE – 因为这是一个真正的计算列会工作。 显然,这阻碍了我选择提供或不提供数据库的价值。

所以,我仍然会问:如何让EF正确地使用定义了DEFAULT CONSTRAINT的数据库列?

这一位是您问题的关键:

因此,我要做的是,如果我没有在对象中明确地为它们设置值,则不要让EF NOT在其INSERT TSQL中包含DEFAULT CONSTRAINT列。

entity framework不会为您做到这一点。 字段要么始终计算,要么始终包含在插入和更新中。 但是,您可以编写类以按照您描述的方式运行。 您必须将字段(显式)设置为构造函数中的默认值,或使用支持字段。

 public class DBUpdateTest /* public partial class DBUpdateTest*/ //version for database first { private _DefValue1 = 200; private _DefValue2 = 30; public DbUpdateTest() { DefSecond = DateTime.Second; } public DefSecond { get; set; } public DefValue1 { get { return _DefValue1; } set { _DefValue1 = value; } } public DefValue2 { get { return _DefValue2; } set { _DefValue2 = value; } } } 

如果你总是使用这些类插入,那么你可能不需要在数据库中设置默认值,但是如果你从其他地方使用sql插入,那么你也必须将默认约束添加到数据库中

我强烈反对DatabaseGeneratedOption.Computed没有帮助停止在insert sql命令中发送字段。 我尝试用最小的小例子来validation它是否有效。

注意 :将DatabaseGeneratedOption.Computed应用于任何属性后,您希望能够指定EF中的任何值。 即插入或更新记录时不能指定任何值。

模型

 public class Person { public int Id { get; set; } public int SomeId { get; set; } public string Name { get; set; } } 

上下文

 public class Context : DbContext { public DbSet People { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().HasKey(d => d.Id); modelBuilder.Entity() .Property(d => d.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); modelBuilder.Entity() .Property(d => d.SomeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); } } 

移民

 public partial class Initial : DbMigration { public override void Up() { CreateTable( "dbo.People", c => new { Id = c.Int(nullable: false, identity: true), SomeId = c.Int(nullable: false, defaultValue:3), //I edited it mannually to assign default value 3. Name = c.String(), }) .PrimaryKey(t => t.Id); } public override void Down() { DropTable("dbo.People"); } } 

注意 :我手动将默认值3编辑为SomeId。

主程序

  static void Main(string[] args) { using (Context c = new Context()) { Person p = new Person(); p.Name = "Jenish"; c.People.Add(p); c.Database.Log = Console.WriteLine; c.SaveChanges(); } } 

我将以下查询记录到我的控制台:

 Opened connection at 04/15/2015 11:32:19 AM +05:30 Started transaction at 04/15/2015 11:32:19 AM +05:30 INSERT [dbo].[People]([Name]) VALUES (@0) SELECT [Id], [SomeId] FROM [dbo].[People] WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity() -- @0: 'Jenish' (Type = String, Size = -1) -- Executing at 04/15/2015 11:32:20 AM +05:30 -- Completed in 3 ms with result: SqlDataReader Committed transaction at 04/15/2015 11:32:20 AM +05:30 Closed connection at 04/15/2015 11:32:20 AM +05:30 

注意SomeId尚未传递给Insert命令,而是在select命令中被选中。

有人在2013/2014回答了这个问题,我相信:)。 该项目正在使用EF5,好吧,请注意! 他告诉我:

  1. 右键单击.edmx ,选择“打开方式”,然后选择“XML(文本)编辑器”,找到在文件的部分中设置为默认约束的列。
  2. 然后在这些字段的末尾添加StoreGeneratedPattern="Computed"

完成,然后它工作。

然后我转移到EF6并且它仍然可以工作,但没有添加之前。

现在,我使用EF6版本6.1.3面临一个非常有趣的问题。 在这个时刻,我有两个带有默认约束的表,将布尔字段设置为1 ,无论记录是ACTIVE还是INACTIVE 。 就那么简单。

在一个表中它起作用,意思是:我不必将此StoreGeneratedPattern="Computed".edmx内的列。 但是,在另一张表中,它没有; 我必须将此StoreGeneratedPattern="Computed"添加到与表1相同的列中。