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

时间:2011-07-26 23:26:50

标签: nhibernate fluent-nhibernate

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

问题是,无论何时在调用合并之前检索要更新的实体(使用获取),它都不起作用(更改不会保留到数据库但没有异常被抛出)。当我删除对获取的调用时,更新实体会起作用。需要了解实体是否存在,因为如果正在创建实体,则需要生成部分复合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();
}

获取调用是在 ScanForInstance 方法中进行的:

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;
    }
}

IdResolver 用于确定应该用于id的内容(映射中单个键的值,否则为具有复合id的实体的对象本身)。

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


数据库是普及的,并且存在一定数量的限制:

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

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

要映射的类型,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中创建了一个控制台项目,其中包含 Program.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中映射。我希望有人会这样做至少是出于好奇,因为我已经开始测试了。

1 个答案:

答案 0 :(得分:3)

好吧,我至少找到了解决问题的办法,但不是为什么。我的解决方案是创建一个新类型,其中包含我用作复合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;
        }
    }
}

然后,将 ParentType 中的属性替换为对此新类型的引用:

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();
    }
}

完成所有这些更改后,即使在合并调用之前调用获取合并仍然有效。我最好的选择是 CompositeId 的非泛型形式没有正确执行某些操作,或者当您在实体上调用合并时,它所做的映射与NH的效果不佳使用它(我想进入FNH的源头来修复它,如果是这种情况,但我已经花了太多时间来弄清楚如何绕过这个问题。)

这一切都很好但是这需要我为我映射的每个实体创建一个新类型,或者至少为具有不同数量的键的id创建一个新类型(即具有2个键的类型,a键入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();
    }
}

我已经使用合并进行了更新和插入测试,并在合并之前调用了获取,并且出乎意料地 IT WORKS 。我仍然处于使用修复的范围(包含复合id或自引用的新类型),因为自我引用对我的口味来说似乎有点笨拙。

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