实体框架不会更新由触发器修改的值

时间:2012-03-08 10:01:38

标签: c# sql-server asp.net-mvc entity-framework

我的表Sections(SQL Server)将ID作为主键(int, identity)SortIndex列(int)进行排序。

数据库有一个触发器,可在每个SortIndex := ID设置INSERT。显然我想稍后通过交换两行的值来改变排序索引。

我使用Entity Framework访问数据,所有这些都使用MVC3 Web应用程序。

问题是,在我将新对象插入表格后,实体框架不会更新SortIndex的值。它还会缓存所有数据,因此以下调用从此表中获取所有对象也会为此对象提供错误的SortIndex值。

我尝试在StoreGeneratedPattern中为此列更改EDMX。这似乎是伟大而优雅的,但并没有解决问题。

如果我设置为Identity,则会导致EF正确更新该值,但它变为只读(尝试更改时抛出异常)。将其设置为Computed是类似的,但不是抛出异常,而是不将值写入数据库。

如果我需要在插入对象后使用它,我可以每次重新创建EF对象,只需执行以下操作:

DatabaseEntities db = new DatabaseEntities()

但对我来说这似乎是一种丑陋的解决方法。

该问题的解决方案是什么?

显然有些事情,不需要我在每insert之后做一些动作(并承担被遗忘和不被注意的风险)是首选。

4 个答案:

答案 0 :(得分:15)

简而言之,StoreGeneratedPattern表示:该值由商店处理,您的应用程序永远不会对其进行修改。在这种情况下,您将在致电SaveChanges后自动获得商店生成的价值。

如果您不使用StoreGeneratedPattern,则无法获得价值,您必须强制执行另一次查询以刷新您的实体。例如,你可以这样做:

objectContext.Refresh(RefreshMode.StoreWins, yourSection);

通常,您需要通过触发器和应用程序更新数据库中的值的情况与EF(以及可能还有其他ORM工具)不能很好地协作。

答案 1 :(得分:3)

我发现'Ladislav Mrnka'的回答是准确的并且标记为已被接受。以下是我在尝试寻找解决方案时发现的其他解决方法。但是,我一直在寻找的解决方案通常是不可能的。

其中一种可能性是设置StoreGeneratedPattern = Computed以让EF知道,计算此值。然后,创建一个存储过程来实际更改SortIndex的值。通常,它会更改两行中的值(交换它们),以更改排序顺序。此过程以及INSERT处的触发器可确保数据在数据库中保持一致。如果在SortIndex中没有设置适当的值,则无法创建新行,不可能使两个对象具有相同的值(除非存储过程有错误)并且不可能以某种方式手动中断该值,因为它是无法通过EF进行编辑。看起来是一个很好的解决方案。

很容易将存储过程映射到EF中的函数。

问题是,现在可以输入新行并且EF正确更新其缓存中的数据,但是在调用存储过程后缓存不会更新。仍然需要一些手动更新或刷新功能。否则,以下调用以SortIndex排序的对象将产生错误的结果。

除此之外,可以为多个实体设置MergeOption = MergeOption.OverwriteChanges,这会导致EF更好地更新数据库中的数据。完成此操作后,可以在插入或调用存储过程后重新读取对象,它将被刷新。但是,使用db.Section.OrderBy(o => o.SortIndex)读取对象集合仍将返回错误排序顺序的缓存结果。

如果有人感兴趣,可以通过添加EF分部类和部分方法MergeOption使OnContextCreated默认为其他内容,如下所示:

public partial class DatabaseEntities
{
    partial void OnContextCreated()
    {
        Subsection.MergeOption = MergeOption.OverwriteChanges;
        Section.MergeOption = MergeOption.OverwriteChanges;
        Function.MergeOption = MergeOption.OverwriteChanges;
    }
}

答案 2 :(得分:0)

您是否知道您是否会在同一请求中再次使用该列?

我会使用每个请求的上下文场景,这通常可以解决许多问题,因为每个请求都会创建一个新的EF上下文,因此每个请求都会有一个新数据。

随着长期生活背景,你所描述的可能会增加不适感。

无论如何,设置为计算的StoreGeneratedPattern应该是正确的。但它只在您存储实际实体时才会更新。它没有通过插入或更新任何其他实体来更新。

来自http://msdn.microsoft.com/en-us/library/dd296755(v=vs.90).aspx

如果您创建新实体或更改现有实体,则在应用程序中调用SaveChanges方法时,将从服务器检索StoreGeneratedPattern设置为Computed的属性值。 如果为应用程序中StoreGeneratedPattern设置为Computed的属性分配值,则在调用SaveChanges方法时,该值将被服务器生成的值覆盖。

我们正在使用SQL序列GUID的计算值选项,它正常工作。

答案 3 :(得分:0)

我在Sql Server Quote表中遇到了类似的情况,该表的varchar QuoteNumber列是非主唯一键,其值由插入后触发器生成。使用触发器是因为生成的值是通过从外键表中获取数据而得出的。 SQL Server架构身份声明不允许您从其他表中提取信息。

我希望EF将此varchar列视为一个标识,并且在更新时不对其进行任何操作,并在插入后重新读取它。如果在其生成的用于配置Entity的代码中的非标识列具有.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;
        }
}