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

时间:2015-04-15 05:15:22

标签: c# sql-server entity-framework 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)的默认值,这完全克服了默认约束。

执行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: How to get EF to handle a Default Constraint

在我的OnModelCreating()类的DbContext方法中,建议我告诉EF具有DEFAULT CONSTRAINT的列是COMPUTED字段,而不是。但是,我想知道在INSERT之后是否会让EF至少读取值(更别提它也可能使我无法为该列赋值,这只是我做的更多)不想要):

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

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

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

2。获取OnModelCreating()来执行? SO: OnModelCreating not called

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

这是我的App.config,这里是<connectionStrings>部分,其中显示了&#34;坏&#34;和#34;好&#34;连接字符串:

<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=&quot;data source=...ConnectStringHere...;App=EntityFramework&quot;"  />
  <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 来生效。

==========尚未解决==========

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

重点是,我有选项没有提供值。我可以提供它,或者我可能无法提供它,如果需要,我可以为每个INSERT执行不同的操作。

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

另外,该选项显然仅用于实际的Computed Columns,而不是用于具有DEFAULT CONSTRAINTs的列,因为一旦应用,模型属性实际上变为READ-ONLY用于INSERT和UPDATE - 因为&#39 ; s真正的计算列如何工作。显然,这阻碍了我选择提供或不提供数据库的价值。

所以,我仍然会问:我怎样才能让EF正常工作&#34;正确&#34;数据库列是否定义了DEFAULT CONSTRAINT?

4 个答案:

答案 0 :(得分:5)

这一位是你问题的关键:

  

因此,我要做的是让EF不包括   如果我没有explcitly,则在INSERT TSQL中使用DEFAULT CONSTRAINT列   在对象中设置它们的值。

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

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插入,那么您还必须将默认约束添加到数据库中

答案 1 :(得分:2)

我强烈反对DatabaseGeneratedOption.Computed无法停止在insert sql命令中发送字段。我尝试用最小的小例来验证它是否有效。

注意:将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<Person> People { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().HasKey(d => d.Id);
        modelBuilder.Entity<Person>()
            .Property(d => d.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Person>()
            .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。

<强> MainProgram(主程序)

    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命令中被选中。

答案 2 :(得分:1)

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

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

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

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

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

答案 3 :(得分:0)

万一有人遇到这个烂摊子...

您确实可以选择将列属性设置为 StoreGeneratedPattern="Computed",但这是一种非此即彼的情况。 在某些情况下,您可能需要覆盖列默认值/约束(我们中的大多数人无论如何都会指定这样的约束,以便 99.9% 的时间我们不必为它发送值)。尽管上述修复程序不允许这样做:(

因此,一旦设置为“Computed”,所有插入/更新操作都会显式忽略该列...