实体框架;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之后采取一些行动(并冒着被遗忘和忽视的风险)的东西是首选。

实体框架;t更新由触发器修改的值

简而言之,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;
        }
}