当之前使用 Get 检索过实体时,无法合并具有复合 ID 的实体

2023-12-15

我正在进行的项目要求我们系统中的数据与另一个系统的数据同步(另一个系统非常流行,这就是同步如此重要的原因)。但是,当我尝试更新具有复合 ID 的现有实体时,我遇到了一个奇怪的问题。

问题是,每当检索要更新的实体时(使用Get) 调用之前Merge,它不起作用(更改不会持久化到数据库,但不会引发异常)。当我删除对Get,更新实体作品。需要了解实体是否存在,因为如果正在创建实体,则需要生成部分复合 ID。

bool exists = ScanForInstance(instance);
using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenSession())
{
    if (exists)
    {
        instance = (T)session.Merge(instance);
    }
    else
    {
        KeyGenerator.Assign<T>(instance);
        newId = session.Save(instance);
    }

    session.Flush();
}

The Get调用是在扫描实例 method:

private bool ScanForInstance<T>(T instance)
    where T : class
{
    var id = IdResolver.ResolveObject<T>(instance);
    using (var session = SessionFactoryFactory.GetSessionFactory<T>().OpenStatelessSession())
    {
        return session.Get<T>(id) != null;
    }
}

The 身份解析器用于确定 id 的用途(映射中单个键的值,否则为具有复合 id 的实体的对象本身)。

就像我说的,如果我删除对Get效果很好。它也适用于所有其他操作(创建、读取和删除)。所有操作(包括更新)对于具有单键的实体都可以正常工作。


DB 是普遍存在的,并且存在一定数量的限制:

  • 不,我无法更改任何模式(我认为这是对 FNB 问题的频繁响应)。
  • 我不想先删除然后插入,因为有些列我们没有同步回我们的系统,我不想删除它们

UPDATED:我添加了一个简单的示例,人们可以复制/粘贴来测试这种奇怪的行为(如果它实际上是通用的)。我希望人们这样做至少可以确认我的问题。

要映射的类型,Fluent 映射:

public class ParentType
{
    public virtual long AssignedId { get; set; }

    public virtual long? GeneratedId { get; set; }

    public virtual string SomeField { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as ParentType);
    }

    private bool Equals(ParentType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();
            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId()
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

不要介意它被称为“ParentType”。我实际上没有任何其他映射,并且在本示例中实际上没有使用该类型作为父类型。之所以这么称呼它,是因为我要提出另一个问题,该问题确实涉及复合 id 和继承的问题(不要使用复合 ID! :-D).

为了进行实际测试,我只是在 VS 中创建了一个控制台项目,并将其作为程序.cs:

static void Main(string[] args)
{
    var smFactory = Fluently.Configure()
        .Database(() => new OdbcPersistenceConfigurer()
            .Driver<OdbcDriver>()
            .Dialect<GenericDialect>()
            .Provider<DriverConnectionProvider>()
            .ConnectionString(BuildSMConnectionString())
            .ProxyFactoryFactory(typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory))
            .UseReflectionOptimizer()
            .UseOuterJoin())
            .Mappings
            (m => 
                m.FluentMappings.Add<ParentMap>()
            );

    var sessionFactory = smFactory.BuildSessionFactory();

    var updatedInstance = new ParentType
    {
        AssignedId = 1,
        GeneratedId = 13,
        SomeField = "UPDATED"
    };

    bool exists;

    using (var session = sessionFactory.OpenStatelessSession())
    {
        exists = session.Get<ParentType>(updatedInstance) != null;
    }

    using (var session = sessionFactory.OpenSession())
    {
        if (exists)
        {
            session.Merge(updatedInstance);

            session.Flush();
        }
    }
}

private static string BuildSMConnectionString()
{
    // Return your connection string here
}

class OdbcPersistenceConfigurer : PersistenceConfiguration<OdbcPersistenceConfigurer, OdbcConnectionStringBuilder>
{

}

我知道添加此示例只是稍微有用一点,因为任何想要测试此示例的人都需要更改 ParentType 字段以符合他们自己的数据库中已有的表,或者添加一个表以匹配 ParentType 中映射的内容。既然我已经在测试方面取得了良好的开端,我希望有人至少会出于好奇而这样做。


好吧,我至少找到了解决问题的方法,但没有找到原因。我的解决方案是创建一个新类型,其中包含我用作复合 id 的属性:

public class CompositeIdType
{
    public virtual long AssignedId { get; set; }

    public virtual long GeneratedId { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as CompositeIdType);
    }

    private bool Equals(CompositeIdType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();

            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

然后,将属性替换为父类型有关此新类型的参考:

public class ParentType
{
    public virtual CompositeIdType Key { get; set; }

    public virtual string SomeField { get; set; }
}

经过这些更改,新的映射将是:

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId<CompositeIdType>(x => x.Key)
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

当所有这些改变完成之后,Merge即使当Get被调用之前Merge称呼。我最好的选择是非通用形式复合ID没有正确执行某些操作,或者当您致电时,它所做的映射与 NH 配合得不好Merge在使用它的实体上(如果是这种情况,我想进入 FNH 的源来修复它,但我已经花了太多时间弄清楚如何绕过这个问题)。

这一切都很好,但这需要我为我映射的每个实体创建一个新类型,或者至少为具有不同数量键的 id 创建一个新类型(即具有 2 个键的类型,具有 3 个键的类型)钥匙等)。

为了避免这种情况,我可以破解它,以便您添加与您正在映射的相同类型的引用并将该引用设置为this在构造函数中:

public class ParentType
{
    public ParentType()
    {
        Key = this;
    }

    public virtual ParentType Key { get; set; }

    public virtual long AssignedId { get; set; }

    public virtual long GeneratedId { get; set; }

    public virtual string SomeField { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as ParentType);
    }

    private bool Equals(ParentType other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return AssignedId == other.AssignedId &&
            GeneratedId == other.GeneratedId;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = GetType().GetHashCode();

            hash = (hash * 31) ^ AssignedId.GetHashCode();
            hash = (hash * 31) ^ GeneratedId.GetHashCode();

            return hash;
        }
    }
}

那么映射将是:

public class ParentMap : ClassMap<ParentType>
{
    public ParentMap()
    {
        Table("STANDARDTASKITEM");

        CompositeId<ParentType>(x => x.Key)
            .KeyProperty(x => x.AssignedId, "STANDARDTASK")
            .KeyProperty(x => x.GeneratedId, "STANDARDTASKITEM");

        Map(x => x.SomeField, "DESCRIPTION");

        Not.LazyLoad();
    }
}

我已经使用它测试了更新和插入Merge with Get在合并之前被调用并且令人惊讶IT WORKS。我仍在犹豫要使用哪种修复(包含复合 id 或自引用的新类型),因为自引用对于我的口味来说似乎有点老套。

如果有人发现为什么这最初不起作用,我仍然想知道......

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当之前使用 Get 检索过实体时,无法合并具有复合 ID 的实体 的相关文章

随机推荐