如何让 EF 6 在 INSERT 期间处理数据库上的 DEFAULT CONSTRAINT
How to get EF 6 to handle DEFAULT CONSTRAINT on a database during INSERT
我是 EF 的新手(这是我的第一周),但对数据库或编程并不陌生。其他人也问过类似的问题,但我觉得问的不是很详细,也不是应该解释的很清楚,所以我去。
问题:
如何让 Entity Framework 正确处理在执行 INSERT 时定义了 DEFAULT CONSTRAINT 的数据库中的列?意思是,如果我在插入操作期间没有在模型中提供值,我如何让 EF 从其生成的 TSQL INSERT 命令中排除该列,以便数据库定义的 DEFAULT CONSTRAINT 起作用?
背景
我创建了一个简单的 table,只是为了测试 Entity Framework 6 (EF6) 及其与列 SQL 服务器的交互是否能够更新。这利用了 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 完全忽略了默认约束的可能性。
这是我用于插入 DBUpdateTest 记录的 EF 代码(我只更新了一个列):
DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();
这是在插入 DBUpdateTest 期间 SQL 生成的 EF(它忠实地更新所有可能的列):
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 列提供零
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
在我的 DbContext
class 的 OnModelCreating()
方法中,建议我可以告诉 EF 具有 DEFAULT CONSTRAINT 的列是一个 COMPUTED 字段,但它不是.但是,我想看看它是否会让 EF 至少在 INSERT 之后读回值(不要介意它也可能使我无法为该列分配值,这只是我所做的更多不想):
modelBuilder.Entity<DBUpdateTest>()
.Property(e => e.DefValue1)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
这是行不通的,实际上看起来并没有什么不同(ED:实际上它确实有效,请参见第 2 点)。 EF 仍然生成相同的 TSQL,为列提供默认值并在此过程中击败数据库。
有没有我遗漏的标志,我忘记设置的配置项,我可以使用的函数属性,我可以创建一些继承的 class 代码,让 EF 到 "handle DEFAULT CONSTRAINT columns correctly?"
2。让 OnModelCreating() 执行? SO: OnModelCreating not called
Janesh(下图)向我展示了如果列标记为 [=,EF 将 从其生成的 TSQL INSERT 命令中删除参数21=]。它对我不起作用,因为显然我使用了错误类型的连接字符串 (!!!)。
这是我的 App.config,这是我显示 "bad" 和 "good" 连接字符串的 <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="data source=...ConnectStringHere...;App=EntityFramework"" />
<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 服务器正在执行此操作,也不必知道默认值是什么或应该是什么。这完全超出了我的控制或知识范围。
重点是,我可以选择不提供值。我可以提供它,也可以不提供它,如果需要,我可以为每个 INSERT 做不同的事情。
对于这种情况,使用 DatabaseGeneratedOption.Computed
实际上不是一个有效的选项,因为它会强制选择:"you can ALWAYS provide a value (and therefore NEVER utilize the database default mechanism), or you can NEVER provide a value (and therefore ALWAYS utilize the database default mechanism)"。
此外,该选项显然仅用于实际的计算列,而不用于具有默认约束的列,因为一旦应用,模型 属性 实际上变为只读以用于 INSERT 和更新 - 因为这就是真正的计算列的工作方式。显然,这阻碍了我选择向数据库提供或不提供值的方式。
所以,我仍然会问:如何让 EF "correctly" 与定义了 DEFAULT CONSTRAINT 的数据库列一起工作?
我强烈不同意 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 命令中 selected。
这一点是您问题的关键:
What I am trying to do, therefore, is to get EF NOT to include the
DEFAULT CONSTRAINT columns in its INSERT TSQL if I do not explcitly
set values for them in the object.
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 进行插入,那么您将不得不也将默认约束添加到数据库
万一有人遇到这个烂摊子...
您确实可以选择将列 属性 设置为 StoreGeneratedPattern="Computed"
,但这是 either/or 的情况。
在某些情况下,您可能需要覆盖列默认值 value/constraint(我们中的大多数人无论如何都会指定这样的约束,因此 99.9% 的时间我们不必为其发送值)。上述修复不允许这样做:(
因此,一旦设置为“Computed
”,所有 Insert/Update 操作都会明确忽略该列...
我是 EF 的新手(这是我的第一周),但对数据库或编程并不陌生。其他人也问过类似的问题,但我觉得问的不是很详细,也不是应该解释的很清楚,所以我去。
问题: 如何让 Entity Framework 正确处理在执行 INSERT 时定义了 DEFAULT CONSTRAINT 的数据库中的列?意思是,如果我在插入操作期间没有在模型中提供值,我如何让 EF 从其生成的 TSQL INSERT 命令中排除该列,以便数据库定义的 DEFAULT CONSTRAINT 起作用?
背景
我创建了一个简单的 table,只是为了测试 Entity Framework 6 (EF6) 及其与列 SQL 服务器的交互是否能够更新。这利用了 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 完全忽略了默认约束的可能性。
这是我用于插入 DBUpdateTest 记录的 EF 代码(我只更新了一个列):
DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();
这是在插入 DBUpdateTest 期间 SQL 生成的 EF(它忠实地更新所有可能的列):
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 列提供零
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
在我的 DbContext
class 的 OnModelCreating()
方法中,建议我可以告诉 EF 具有 DEFAULT CONSTRAINT 的列是一个 COMPUTED 字段,但它不是.但是,我想看看它是否会让 EF 至少在 INSERT 之后读回值(不要介意它也可能使我无法为该列分配值,这只是我所做的更多不想):
modelBuilder.Entity<DBUpdateTest>()
.Property(e => e.DefValue1)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
这是行不通的,实际上看起来并没有什么不同(ED:实际上它确实有效,请参见第 2 点)。 EF 仍然生成相同的 TSQL,为列提供默认值并在此过程中击败数据库。
有没有我遗漏的标志,我忘记设置的配置项,我可以使用的函数属性,我可以创建一些继承的 class 代码,让 EF 到 "handle DEFAULT CONSTRAINT columns correctly?"
2。让 OnModelCreating() 执行? SO: OnModelCreating not called
Janesh(下图)向我展示了如果列标记为 [=,EF 将 从其生成的 TSQL INSERT 命令中删除参数21=]。它对我不起作用,因为显然我使用了错误类型的连接字符串 (!!!)。
这是我的 App.config,这是我显示 "bad" 和 "good" 连接字符串的 <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="data source=...ConnectStringHere...;App=EntityFramework"" />
<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 服务器正在执行此操作,也不必知道默认值是什么或应该是什么。这完全超出了我的控制或知识范围。
重点是,我可以选择不提供值。我可以提供它,也可以不提供它,如果需要,我可以为每个 INSERT 做不同的事情。
对于这种情况,使用 DatabaseGeneratedOption.Computed
实际上不是一个有效的选项,因为它会强制选择:"you can ALWAYS provide a value (and therefore NEVER utilize the database default mechanism), or you can NEVER provide a value (and therefore ALWAYS utilize the database default mechanism)"。
此外,该选项显然仅用于实际的计算列,而不用于具有默认约束的列,因为一旦应用,模型 属性 实际上变为只读以用于 INSERT 和更新 - 因为这就是真正的计算列的工作方式。显然,这阻碍了我选择向数据库提供或不提供值的方式。
所以,我仍然会问:如何让 EF "correctly" 与定义了 DEFAULT CONSTRAINT 的数据库列一起工作?
我强烈不同意 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 命令中 selected。
这一点是您问题的关键:
What I am trying to do, therefore, is to get EF NOT to include the DEFAULT CONSTRAINT columns in its INSERT TSQL if I do not explcitly set values for them in the object.
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 进行插入,那么您将不得不也将默认约束添加到数据库
万一有人遇到这个烂摊子...
您确实可以选择将列 属性 设置为 StoreGeneratedPattern="Computed"
,但这是 either/or 的情况。
在某些情况下,您可能需要覆盖列默认值 value/constraint(我们中的大多数人无论如何都会指定这样的约束,因此 99.9% 的时间我们不必为其发送值)。上述修复不允许这样做:(
因此,一旦设置为“Computed
”,所有 Insert/Update 操作都会明确忽略该列...