【问题标题】:Fluent NHibernate - Unnecessary updateFluent NHibernate - 不必要的更新
【发布时间】:2012-06-05 03:08:47
【问题描述】:

RegistrationItem 之间的单向多对多关系中,Registration 有一个ISet<Item> ItemsPurchased,而Item 没有引用回注册(这不是探索对象图),当我查看正在生成的 SQL 时,我看到了

INSERT INTO Registrations_Items (RegistrationId, ItemId) VALUES (@p0, @p1);@p0 = 1 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]
UPDATE Items SET Price = @p0, Name = @p1, [...], ListIndex = @p5, EventId = @p6 WHERE ItemId = @p7

传递给更新的参数是正确的,但是Item没有任何变化,所以不需要更新。

映射是通过自动映射 Registration 的覆盖,而 Item 没有覆盖。 DB Schema 看起来完全正确。我删除了所有约定并再次测试,并且行为仍然存在,因此不是我的任何映射约定都在这样做。

mapping.HasManyToMany(e => e.ItemsPurchased).AsSet().Cascade.All().Not.Inverse();

为什么 NHibernate 会发出这个 UPDATE 电话,我该怎么做才能阻止它?这并没有真正伤害任何东西,但它表明我做错了什么,所以我想弄清楚是什么。

编辑: 根据下面的评论,我创建了一个单元测试,它创建了一个EventItem 必须属于一个Event),向其中添加两个Items,从会话中驱逐第一个并刷新会话,然后获取第一个返回通过它的 ID。

我注意到下面的 SELECT 项目行中有一些奇怪的地方(倒数第二个)

INSERT INTO Events (blah blah blah...)
select @@IDENTITY
INSERT INTO Items (Price, Name, StartDate, EndDate, ExternalID, ListIndex, EventId) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);@p0 = 100.42 [Type: Decimal (0)], @p1 = 'Item 1' [Type: String (0)], @p2 = NULL [Type: DateTime (0)], @p3 = NULL [Type: DateTime (0)], @p4 = '123' [Type: String (0)], @p5 = 0 [Type: Int32 (0)], @p6 = 1 [Type: Int32 (0)]
select @@IDENTITY
SELECT blah blah blah FROM Events event0_ WHERE event0_.EventId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT itemsforsa0_.EventId as EventId1_, itemsforsa0_.ItemId as ItemId1_, itemsforsa0_.ListIndex as ListIndex1_, itemsforsa0_.ItemId as ItemId3_0_, itemsforsa0_.Price as Price3_0_, itemsforsa0_.Name as Name3_0_, itemsforsa0_.StartDate as StartDate3_0_, itemsforsa0_.EndDate as EndDate3_0_, itemsforsa0_.ExternalID as ExternalID3_0_, itemsforsa0_.ListIndex as ListIndex3_0_, itemsforsa0_.EventId as EventId3_0_ FROM Items itemsforsa0_ WHERE itemsforsa0_.EventId=@p0;@p0 = 1 [Type: Int32 (0)]
UPDATE Items SET Price = @p0, Name = @p1, StartDate = @p2, EndDate = @p3, ExternalID = @p4, ListIndex = @p5, EventId = @p6 WHERE ItemId = @p7;@p0 = 100.42000 [Type: Decimal (0)], @p1 = 'Item 1' [Type: String (0)], @p2 = NULL [Type: DateTime (0)], @p3 = NULL [Type: DateTime (0)], @p4 = '123' [Type: String (0)], @p5 = 0 [Type: Int32 (0)], @p6 = 1 [Type: Int32 (0)], @p7 = 1 [Type: Int32 (0)]

表创建正确:

create table Items (
    ItemId INT IDENTITY NOT NULL,
   Price NUMERIC(19,5) not null,
   Name NVARCHAR(255) not null,
   StartDate DATETIME null,
   EndDate DATETIME null,
   ExternalID NVARCHAR(255) not null,
   ListIndex INT not null,
   EventId INT not null,
   primary key (ItemId)
)

DateTimes 故意可以为空,因为项目可能不需要特定日期(例如“早鸟注册”)。

【问题讨论】:

  • 要确认幻影更新问题,Get 同一项目并检查是否在刷新/提交时发布了更新。然后您可以排除任何多对多级联问题。
  • 这可能与有一个小数字段有关吗? Google 为 Phantom Updates 和 decimal 提供了一些结果,但我还没有深入了解他们所说的内容。

标签: nhibernate fluent-nhibernate nhibernate-mapping fluent-nhibernate-mapping


【解决方案1】:

如上所述(如下?谁知道。寻找​​我在另一个答案上留下的评论),我注意到CanGenerateDatabaseSchema 单元测试和CanGetItem 单元测试之间的区别在于一个给我@ 987654323@,另一个给我DECIMAL (19,0)

我四处寻找,发现CanGenerateDatabaseSchema 正在使用我的“真实”配置(来自 Web 项目),而另一个测试正在使用我的“单元测试”配置。我的单元测试是针对 Sql Server CE 运行的......当我将单元测试更改为使用与我的真实数据库(Sql Server 2005)相同的配置时,幻象更新突然消失了。

因此,如果其他人遇到带有小数的意外幻像更新...请检查您是否使用的是 Sql Server CE。由于测试实际上通过了(说它失败的评论是不正确的,它没有失败,只是做额外的工作),我想我会接受它,尽管为什么 Sql CE 忽略我的配置是一个很好的问题,并且可能的 NH 或 FNH 错误。

【讨论】:

    【解决方案2】:

    这叫做:Phantom Updates,它通常和你的对象的映射有关

    这是主要原因:

    想象一下我们有一个像这样的对象

    public class Product
    {
       public Guid Id { get; set; }
       public int ReorderLevel { get; set; }
       public decimal UnitPrice { get; set; }
    }
    

    还有一张地图:

    public class ProductMap : ClassMap<Product>
    {
       public ProductMap()
       {
          Not.LazyLoad();
          Id(x => x.Id).GeneratedBy.GuidComb();
          Map(x => x.ReorderLevel);
          Map(x => x.UnitPrice).Not.Nullable();
       }
    }
    

    请注意,ReorderLevel 将接受空值

    如果您在没有指定ReorderLevel 的情况下保存此实体,它将使用null 值保存,但是当您从数据库中加载它时,由于ReorderLevel 类型是int,一个0 将被添加,这将导致实体被标记为脏,因此将导致更新

    这类错误很难检测和跟踪,我建议您在数据库中确实需要 null 时使用Nullable&lt;&gt; 类型

    我通常实现这一点的方法是创建一个约定,如果用Nullable&lt;&gt; 声明它们,它将自动将我的Value Types 设置为null,否则该字段将被标记为NotNullable

    作为补充,这就是我的约定的样子:

        mapper.BeforeMapProperty += (ins, memb, cust) =>
        {
            var type = memb.LocalMember.GetPropertyOrFieldType();
    
            if (type.IsValueType)
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    cust.Column(x => { x.NotNullable(notnull: false); });
                }
                else
                {
                    cust.Column(x => { x.NotNullable(notnull: true); });
                }
            }
        }
    

    【讨论】:

    • 我已经有一个ColumnNullConvention' which is ensuring that any field marked with DataAnnotations.Required` 被声明为非空。
    • 我已经编辑了原始问题,以显示简单的 create-evict-flush-get 循环的 SQL 并包含表映射。
    • 问题在于价格field,如您所见,它插入了值 100.42 但随后更新为 100.42000 我认为这将实体标记为脏。您是否有自定义逻辑来格式化Price 字段?您也可以使用Price 字段及其实体的映射来更新帖子吗?
    • Price 字段是一个 C# decimal?,具有简单的 get/set 和没有自定义逻辑。视图负责对其进行格式化以供显示。它的映射现在是 mapping.Map(i => i.Price).Precision(6).Scale(2);虽然这并没有改变任何东西。
    • 如果价格是decimal?,只需观察一下为什么它在数据库中被标记为NotNull
    猜你喜欢
    • 2022-07-04
    • 2012-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多