26

我是 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 时,它为可为空或不可为空的类型提供通用默认值,就像该列上没有定义默认约束一样。因此,显然 EF 完全忽略了默认约束的可能性。

这是我插入 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 列提供零

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 处理默认约束

OnModelCreating()我的课堂方法中DbContext,建议我可以告诉 EF 具有 DEFAULT CONSTRAINT 的列是 COMPUTED 字段,但事实并非如此。但是,我想看看它是否会让 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=&quot;data source=...ConnectStringHere...;App=EntityFramework&quot;"  />
  <add name="TEST_EF6Entities_IT_WORKS" providerName="System.Data.SqlClient" connectionString="data source=...ConnectStringHere...;App=EntityFramework;"  />
</connectionStrings>

区别:起作用的使用ProviderNameSystem.Data.SqlClient不起作用的使用System.Data.EntityClient。显然 SqlClient 提供程序允许OnModelCreating()调用该方法,这使我的使用DatabaseGeneratedOption.Computed产生了效果。

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

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

关键是,我可以选择不提供价值。我可以提供它,也可以不提供它,如果需要,我可以对每个 INSERT 执行不同的操作。

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

另外,该选项显然只用于实际的计算列,而不是用于具有默认约束的列,因为一旦应用,模型属性实际上会变为只读以用于插入和更新 - 因为这就是真正的计算列会工作。显然,这妨碍了我选择向数据库提供或不提供值的方式。

所以,我仍然要问:如何让 EF 与定义了 DEFAULT CONSTRAINT 的数据库列“正确”工作?

4

3 回答 3

5

这一点是您问题的关键:

因此,如果我没有在对象中明确设置它们的值,我想要做的是让 EF 不在其 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 插入,那么您也必须将默认约束添加到数据库中

于 2015-04-15T08:28:04.983 回答
3

我强烈不同意这DatabaseGeneratedOption.Computed无助于停止在插入 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。

主程序

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

于 2015-04-15T06:04:34.823 回答
-1

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

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

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

于 2021-06-05T19:11:09.707 回答