C# EF6 代码优先实体状态

Posted

技术标签:

【中文标题】C# EF6 代码优先实体状态【英文标题】:C# EF6 code-first EntityState 【发布时间】:2017-01-03 04:44:59 【问题描述】:

我有一个使用 EF6 更新数据的 C# Web。并且在更新期间将触发 SQL Server 触发器。触发器应该通过

找到更新的列
SELECT  
    @Columns_Updated = ISNULL(@Columns_Updated + ',', '') + name 
FROM    
    syscolumns 
WHERE   
    id = @idTable   
    AND CONVERT(VARBINARY, REVERSE(COLUMNS_UPDATED())) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0

我在下面的陈述中有一个问题

CrmClientContact t1 = dbdb.CrmClientContact.Where(x => x.rowId == 4).FirstOrDefault();
t1.updatedAt = DateTime.Now;
dbdb.Entry(t1).State = EntityState.Modified;
dbdb.SaveChanges();

CrmClientBetLimit t2 = dbdb.CrmClientBetLimit.Where(x => x.rowId == 1028).FirstOrDefault();
t2.updatedAt = DateTime.Now;
dbdb.Entry(t2).State = EntityState.Modified;
dbdb.SaveChanges();

CrmClientCLState t3 = dbdb.CrmClientCLState.Where(x => x.rowId==1).FirstOrDefault();
t3.updatedAt = DateTime.Now;
dbdb.Entry(t3).State = EntityState.Modified;
dbdb.SaveChanges(); 

这是我在 EF6 框架中更新记录的通常做法。

如图所示,我已经更新了这 3 个表中的字段 updatedAt

但是,触发器显示完全不同@Columns_Updated

表 1:clientId, createdAt, createdBy, name, rowId for t1(表中有 11 列)

表 2:betLimitValueSum、clientId、createdAt、createdBy、currId、updatedAt、updatedBy for t2(表中有 8 列)

表 3. t3 的 rowId(表中有 9 列)

我找不到他们返回这些列的原因。

注意:两个表都包含列createdAt, createdBy, updatedAt, updatedBy

为了解决这个问题,我尝试从代码中删除这些语句

dbdb.Entry(t1).State = EntityState.Modified;  
dbdb.Entry(t2).State = EntityState.Modified;  
dbdb.Entry(t3).State = EntityState.Modified;  

因此,我认为问题在于在db.SaveChange() 之前附加EntityState

我想知道

    为什么附加EntityState 会导致数据表中出现那些意外更新列?

    什么时候应该附上EntityState.Modified(我认为像上面的代码一样更新记录时这样做是合适的,但显然触发器显示它不是)?

-----------------更新----------------- 从代码中删除 EntityState.Modified 后。触发器仍然无法接收到正确的@UpdatedColumns,我尝试了下面的代码

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 7).FirstOrDefault();
t3.updatedAt = DateTime.Now;
t3.updatedBy = DateTime.Now.ToString();
t3.mobile = DateTime.Now.ToString();
t3.name = DateTime.Now.ToString();
dbdb.SaveChanges();

但是,在触发器中,它返回 createdAt,rowId 作为更新的列。请注意,某些表格可以正常工作。

而且这种行为对我来说是出乎意料的。举例

CrmClientContact t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now;
t3.email = DateTime.Now.ToString();
dbdb.SaveChanges(); // correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now.AddDays(1);
t3.email = DateTime.Now.ToString() + 1;
dbdb.SaveChanges(); //correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now.AddDays(2);
t3.email = DateTime.Now.ToString() + 2;
t3.mobile = DateTime.Now.ToString() + 2;
t3.name = DateTime.Now.ToString() + 2;
dbdb.SaveChanges(); // correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now;
t3.updatedBy = DateTime.Now.ToString();                      
dbdb.SaveChanges(); // correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now.AddDays(1);
t3.updatedBy = DateTime.Now.ToString()+1;
dbdb.SaveChanges(); // correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedAt = DateTime.Now.AddDays(2);
t3.updatedBy = DateTime.Now.ToString() + 2;
dbdb.SaveChanges(); // correct

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedBy =3+ DateTime.Now.ToString();
t3.email =1+ DateTime.Now.ToString();
dbdb.SaveChanges();//null, incorrect 

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedBy =2+ DateTime.Now.ToString();
t3.email =1+ DateTime.Now.ToString();
dbdb.SaveChanges();//updatedBy, incorrect (email is missing)

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedBy = 22 + DateTime.Now.ToString();
t3.email = 11 + DateTime.Now.ToString();
dbdb.SaveChanges();//null, incorrect 

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedBy = 222 + DateTime.Now.ToString();
t3.email = 111 + DateTime.Now.ToString();
dbdb.SaveChanges();//null, incorrect 

t3 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t3.updatedBy = 22 + DateTime.Now.ToString();
t3.email = 11 + DateTime.Now.ToString();
dbdb.SaveChanges();//null, incorrect 
CrmClientContact t4 = dbdb.CrmClientContact.Where(x => x.rowId == 5).FirstOrDefault();
t4.updatedBy = 2 + DateTime.Now.ToString();
t4.email = 1 + DateTime.Now.ToString();
dbdb.SaveChanges();//null, incorrect 

如上所示,我完全不明白上述结果集是如何产生的(它们是确定性的,可重复的)

--------------最终更新 -------------- 使用 SQL Profiler 后,我发现 EF6 的 SQL 查询只是一个与文档匹配的普通更新语句。并且在管理工作室执行相同的SQL时可以重现相同的结果(在触发器中找不到UPDATED COLUMN)。

结果,我认为SQL Server Update Trigger, Get Only modified fields 只能用于以前版本的SQL Server。

至少我的数据库 (SQLSERVER 2014 (120)) 无法应用标记的答案来查找更新的列。

最后,我应用了另一个对已删除和插入的表进行透视并找到不同的方法,没有理由再失败,除非它需要表中未更改的列(幸运的是,我所有的表都有应该未更改的主键) .

唯一的问题是维护每个表修改的触发器。

谢谢。

【问题讨论】:

【参考方案1】:

来自MSDN:

当您将状态更改为已修改时,实体的所有属性将被标记为已修改,并且所有属性值将在调用 SaveChanges 时发送到数据库。

当您从数据库中检索实体对象时,EF 会为您跟踪实体对象的状态。这意味着更改任何属性值并使用 SaveChanges 保存实体也会保留您的更改,并且您无需将实体状态显式标记为 EntityState.Modified

希望这会有所帮助。

附:如果您有审计需求,可以查看EntityFramework-Plus Audit。我建议,因为我们在工作中使用它,我与“EntityFramework-Plus”没有任何关系。

【讨论】:

奇怪的是附加 EntityState 并没有更新所有列。我认为这是因为 SQL 语句不适用于查找更新的列。但是,如果我直接在管理工作室中执行 SQL,我可以正确获取更新的列。这是我真正怀疑的主要问题。事实上,我只想使用 EF6 执行“update t1 set updatedAt=GETDATE() where rowid=4”,这样我就可以在触发器中以 @UpdatedColumn 的形式获取 updateAt。不幸的是,上面的 EF6 更新不起作用(它返回奇怪的列!) @SKLTFZ 在这种情况下,我建议记录 sql 查询。您可以查看msdn.microsoft.com/en-us/library/…。或者,您可以运行 SQL Server Profiler(可从 SSMS 获得)来检查正在对您的数据库执行哪些查询。

以上是关于C# EF6 代码优先实体状态的主要内容,如果未能解决你的问题,请参考以下文章

EF6 使用列默认值创建代码优先表

EF6 模型优先 - 实体类型不是当前上下文模型的一部分

实体框架代码的动态连接字符串首先在运行时 EF6(mssql、mysql、oracle)中用于多个数据库提供程序

EF 代码优先 - 具有子实体的序列号

C# - 实体框架代码优先,延迟加载不起作用

EF 6.X 中的实体框架代码优先 Fluent API 默认值