我是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 另一方面,如果我执行以下语句: 我会得到一张这样的唱片 这与预期的一样有效,原因有一:TSQLINSERT命令没有为具有定义的DEFAULTCONSTRAINT的任何列提供值。 因此,如果我没有在模型对象中明确设置DEFAULT CONSTRAINT列的值,我要做的是让EF从INSERT TSQL中排除这些列。 我已经尝试过的东西 1.是否识别默认约束SO:如何让EF处理默认约束 在我的 这不起作用,事实上似乎没有什么不同(ED:实际上它起作用了,见第2点)。EF仍然生成相同的TSQL,为列提供默认值,并在此过程中击败数据库。 是否有我丢失的标志,我忘记设置的配置项,我可以使用的函数属性,我可以创建的一些继承类代码,以使EF"正确处理DEFAULT CONSTRAINT列?"> 2.是否让OnModelCreating()执行SO:OnModelCreating未调用 Janesh(如下)向我展示,如果列用 这是我的App.config,这是 区别:有效的使用 =========尚未解决====== 对列使用DEFAULT CONSTRAINTS的目的是允许我提供(或不提供)一个值,并且最终在数据库端使用一个有效值。我不必知道SQL Server在做这件事,也不必知道默认值是什么或应该是什么。这完全超出了我的控制或知识范围。 关键是,我可以选择不提供该值。我可以提供它,也可以不提供它,如果需要,我可以对每个INSERT执行不同的操作。 对于这种情况,使用 此外,该选项显然只用于实际的Computed Columns,而不用于具有DEFAULT CONSTRAINTs的列,因为一旦应用,为了INSERT和UPDATE的目的,模型属性实际上变为READ-only,因为真正的Computed Column就是这样工作的。显然,这妨碍了我选择是否向数据库提供值。 因此,我仍然要问:如何让EF"正确"处理定义了DEFAULT CONSTRAINT的数据库列?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
DbContext
类的OnModelCreating()
方法中,建议我可以告诉EF具有DEFAULT CONSTRAINT的列是COMPUTED字段,但事实并非如此。然而,我想看看它是否能让EF在INSERT之后至少读取回值(别介意它也可能会让我无法为该列分配值,这只是我不想要的更多内容):modelBuilder.Entity<DBUpdateTest>()
.Property(e => e.DefValue1)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
DatabaseGeneratedOption.Computed
标记,则EF将从其生成的TSQLINSERT命令中删除参数。这对我来说不起作用,因为显然我使用了错误的连接字符串(!!!)。<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="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
产生效果。DatabaseGeneratedOption.Computed
实际上不是一个有效的选项,因为它迫使您做出选择:"您可以始终提供一个值(因此永远不要使用数据库默认机制),或者您永远不能提供值(因此始终使用数据库默认机制)"。
这个位是您问题的关键:
因此,我想做的是让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
",所有插入/更新操作都会显式忽略该列。。。