繁体   English   中英

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

[英]Entity Framework doesn't update value which is modified by a trigger

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

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

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

问题是,在我将新对象插入表后,Entity Framework不会更新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排序的SortIndex将产生错误的结果。

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

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

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

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

我希望EF将此varchar列视为标识,并在更新时对其执行任何操作,并在插入后重新读取它。 如果.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 Reverse POCO Generator生成。 起初,我无法弄清楚如何使生成器将此属性添加到未在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;
        }
}

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

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

对于长期存在的背景,如您所述,可能会出现紧张情绪。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM