实体框架;t更新由触发器修改的值
本文关键字:触发器 修改 更新 框架 实体 | 更新日期: 2023-09-27 18:27:19
我的表Sections
(SQL Server)将ID
作为主键(int, identity)
,SortIndex
列(int)用于排序。
该数据库具有触发器,该触发器在每个INSERT
处设置SortIndex := ID
。显然,我想稍后通过交换两行的值来更改排序索引。
我使用实体框架访问数据,所有这些都使用MVC3 web应用程序。
问题是,在我向表中插入新对象后,实体框架不会更新SortIndex
的值。它还缓存所有数据,因此下面从该表中获取所有对象的调用也会为该对象提供错误的SortIndex
值。
我尝试在EDMX
中更改此列的StoreGeneratedPattern
。这看起来很棒,但并不能解决问题。
如果我设置为Identity
,它会导致EF正确更新值,但它变为只读(尝试更改时引发异常)。将其设置为Computed
是类似的,但不是抛出异常,而是不将值写入DB。
如果在插入对象后需要使用EF对象,我可以每次重新创建它,只需执行以下操作:
DatabaseEntities db = new DatabaseEntities()
但对我来说,这似乎是一个丑陋的变通办法。
这个问题的解决办法是什么?
显然,不需要我在每次insert
之后采取一些行动(并冒着被遗忘和忽视的风险)的东西是首选。
简而言之,StoreGeneratedPattern
的意思是:值由存储处理,您的应用程序永远不会修改它。在这种情况下,您将在调用SaveChanges
后自动获得存储生成的值。
如果不使用StoreGeneratedPattern
,则不会获得值,并且必须强制执行另一个查询来刷新实体。例如,你可以做:
objectContext.Refresh(RefreshMode.StoreWins, yourSection);
通常,在需要通过触发器和应用程序更新数据库中的值的情况下,EF(可能还有其他ORM工具)不能很好地发挥作用。
我发现"Ladislav Mrnka"的答案是准确的,并将其标记为已接受。以下是我在试图找到解决方案时发现的其他解决方案。然而,我所寻求的解决方案通常是不可能的。
其中一种可能性是设置StoreGeneratedPattern = Computed
让EF知道,这个值是计算出来的。然后,制作一个存储过程来实际更改SortIndex
的值。通常,它会更改两行中的值(交换它们),以更改排序顺序。此过程与INSERT
处的触发器一起保证数据在数据库中保持一致。如果没有在SortIndex
中设置正确的值,就不可能创建新行,也不可能使两个对象具有相同的值(除非存储过程有错误),也不可以手动断开值,因为无法通过EF进行编辑。看起来是个不错的解决方案。
将存储过程映射到EF中的函数是很容易的。
问题是,现在可以输入新行,EF会正确地更新其缓存中的数据,但在调用存储过程后不会更新缓存。仍然需要一些手动更新或刷新功能。否则,下面调用以获取按SortIndex
排序的对象将给出错误的结果。
除此之外,还可以为多个实体设置MergeOption = MergeOption.OverwriteChanges
,这会使EF更好地更新DB中的数据。通过这样做,可以在插入对象或调用存储过程后重新读取对象,并且它将被刷新。但是,使用db.Section.OrderBy(o => o.SortIndex)
读取对象集合仍然会返回排序顺序错误的缓存结果。
如果有人感兴趣,可以通过添加EF分部类,然后添加分部方法OnContextCreated
,使MergeOption
默认为其他内容,如下所示:
public partial class DatabaseEntities
{
partial void OnContextCreated()
{
Subsection.MergeOption = MergeOption.OverwriteChanges;
Section.MergeOption = MergeOption.OverwriteChanges;
Function.MergeOption = MergeOption.OverwriteChanges;
}
}
您知道是否会在同一请求中再次使用该列吗?
我会使用每个请求的上下文场景,这通常会让你摆脱很多问题,因为每个请求都会创建一个新的EF上下文,所以每个请求都有一个新数据。
在长期存在的背景下,可能会出现你所描述的不一致。
无论如何,StoreGeneratedPattern设置为computed应该是正确的。但只有当您存储实际实体时,它才会更新自己。它不会通过插入或更新任何其他实体来更新。
来自http://msdn.microsoft.com/en-us/library/dd296755(v=vs.90).aspx
如果创建新实体或更改现有实体,则在应用程序中调用SaveChanges方法时,将从服务器检索StoreGeneratedPattern设置为Computed的属性的值。如果将值分配给应用程序中StoreGeneratedPattern设置为Computed的属性,则在调用SaveChanges方法时,该值将被服务器生成的值覆盖。
我们正在为SQL排序的GUID使用计算值选项,它运行正常。
我在SQL Server Quote表中遇到过类似的情况,该表具有varchar QuoteNumber列,该列是一个非主唯一键,其值由插入后触发器生成。之所以使用触发器,是因为生成的值是通过从外键表获取数据派生的。Sql Server架构标识声明不允许从其他表中提取信息。
我希望EF将这个varchar列视为一个标识,在更新时不对其执行任何操作,并在插入后重新读取它。如果EF为配置实体而生成的代码中的非标识列具有.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)属性,则EF会这样做(向右滚动):
public QuoteConfiguration(string schema)
{
ToTable("Quote", schema);
HasKey(x => x.ID);
Property(x => x.ID).HasColumnName(@"ID").HasColumnType("int").IsRequired().HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
Property(x => x.QuoteNumber).HasColumnName(@"Quote_Number").HasColumnType("varchar").IsOptional().IsUnicode(false).HasMaxLength(64).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
}
我的EF模型是代码先行的,由Simon Hughes的EntityFramework反向POCO生成器生成。起初,我不知道如何让生成器将此属性添加到Sql Server中未声明为标识的列中。
插入后重读整个Quote实体不会检索到自动生成的QuoteNumber。然后我发现在插入后重新读取QuoteNumber列会破坏实体缓存。但是,我觉得这样做很肮脏。
最后,我与Simon Hughes合作,发现如何让他的EF Reverse POCO为我做这件事。你只需在*.tt文件中扩展UpdateColumn函数,如下所示:
Settings.UpdateColumn = (Column column, Table table) =>
{
if (table.Name.Equals("Quote", StringComparison.InvariantCultureIgnoreCase)
&& column.Name.Equals("Quote_Number", StringComparison.InvariantCultureIgnoreCase))
{
column.IsStoreGenerated = true;
}
}