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



我是EF的新手(第一周),但对数据库或编程并不陌生。其他人也问过类似的问题,但我觉得没有人问到正确的细节,也没有像需要解释的那样解释清楚,所以我开始了。

问题:如何让实体框架正确处理数据库中在执行INSERT时定义了DEFAULT CONSTRAINT的列?也就是说,如果我在插入操作期间没有在模型中提供值,我如何让EF从其生成的TSQL insert命令中排除该列,以便数据库定义的DEFAULT CONSTRAINT能够工作?

背景

我创建了一个简单的表,只是为了测试EntityFramework6(EF6)及其与SQLServer能够更新的列的交互。这利用了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时,它会为可为null或不可为null的类型提供公共默认值,就好像该列上没有定义default CONSTRAINT一样。因此,很明显,EF完全忽略了default CONSTRAINT的可能性。

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

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

以下是EF在INSERT到DBUpdateTest期间生成的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这就是执行EF INSERT命令时发生的情况,它为可为NULL的列提供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

这与预期的一样有效,原因有一:TSQLINSERT命令没有为具有定义的DEFAULTCONSTRAINT的任何列提供值。

因此,如果我没有在模型对象中明确设置DEFAULT CONSTRAINT列的值,我要做的是让EF从INSERT TSQL中排除这些列。

我已经尝试过的东西

1.是否识别默认约束SO:如何让EF处理默认约束

在我的DbContext类的OnModelCreating()方法中,建议我可以告诉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(如下)向我展示,如果列用DatabaseGeneratedOption.Computed标记,则EF从其生成的TSQLINSERT命令中删除参数。这对我来说不起作用,因为显然我使用了错误的连接字符串(!!!)。

这是我的App.config,这是<connectionStrings>部分,我在其中显示了"坏"one_answers"好"连接字符串:

<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.SqlClientProviderName,不起作用的使用System.Data.EntityClient。显然,SqlClient提供程序允许调用OnModelCreating()方法,这允许我使用DatabaseGeneratedOption.Computed产生效果。

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

对列使用DEFAULT CONSTRAINTS的目的是允许我提供(或不提供)一个值,并且最终在数据库端使用一个有效值。我不必知道SQL Server在做这件事,也不必知道默认值是什么或应该是什么。这完全超出了我的控制或知识范围。

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

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

此外,该选项显然只用于实际的Computed Columns,而不用于具有DEFAULT CONSTRAINTs的列,因为一旦应用,为了INSERT和UPDATE的目的,模型属性实际上变为READ-only,因为真正的Computed Column就是这样工作的。显然,这妨碍了我选择是否向数据库提供值。

因此,我仍然要问:如何让EF"正确"处理定义了DEFAULT CONSTRAINT的数据库列?

这个位是您问题的关键:

因此,我想做的是让EF不包括DEFAULT CONSTRAINT列在其INSERT TSQL中(如果我没有解释)在对象中为它们设置值。

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

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命令中停止发送字段的说法。我试着用一个很小的例子来验证,它起了作用。

注意:一旦将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命令中选择的

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

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

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

相关内容

  • 没有找到相关文章

最新更新