我的表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
之后做一些动作(并承担被遗忘和不被注意的风险)是首选。
答案 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;
}
}