当此实体先前已使用Get进行检索时,无法合并具有Composite ID的实体 [英] Cannot Merge an entity with a Composite Id when that entity has been previously retrieved using Get

查看:142
本文介绍了当此实体先前已使用Get进行检索时,无法合并具有Composite ID的实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

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

session.Flush();

获取调用在 ScanForInstance 方法:

pre $ private $ b $ ScanForInstance< T>(T实例)
其中T:class
{
var id = IdResolver.ResolveObject< T>(instance);
using(var session = SessionFactoryFactory.GetSessionFactory< T>().OpenStatelessSession())
{
return session.Get< T>(id)!= null;




$ b

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

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




数据库是普遍存在的,的限制:


  • 不,我不能改变任何模式(我认为这是对FNB问题的频繁回应) li>
  • 我不想只是删除然后插入,因为有些列我们没有同步回到我们的系统,我不想擦掉这些。
  • 我已经添加了一个简单的例子,人们可以复制/粘贴来测试这个怪异的行为(如果它实际上是普遍的)。我希望人们至少可以确认我的问题。

    b
    $ b

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

    public virtual long? GeneratedId {get;组; }

    公共虚拟字符串SomeField {get;组; }
    $ b $ public override bool Equals(object obj)
    {
    return Equals(obj as ParentType);

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

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

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

    返回散列;
    }
    }
    }

    公共类ParentMap:ClassMap< ParentType>
    {
    public ParentMap()
    {
    表(STANDARDTASKITEM);

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

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

    Not.LazyLoad();




    $ b $ p $不必介意它被称为'ParentType 。'我实际上没有任何其他的映射,在这个例子中并没有实际使用这个类型作为父类型。这被称为是因为我打开另一个涉及组合ID和继承问题的问题(不要使用组合ID!!-D )。



    对于实际的测试,我刚刚在VS中创建了一个控制台项目,它是 Program.cs

    <$ p

    $ b $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $。$ )
    .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();
    $ b $ var updateInstance = new ParentType
    {
    AssignedId = 1,
    GeneratedId = 13,
    SomeField =UPDATED
    };

    bool exists;

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


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

    session.Flush();



    $ private static string BuildSMConnectionString()
    {
    //返回你的连接字符串
    }

    class OdbcPersistenceConfigurer:PersistenceConfiguration< OdbcPersistenceConfigurer,OdbcConnectionStringBuilder>
    {

    }

    我知道添加这个示例是只是稍微有点帮助,因为任何人想要测试这将需要更改ParentType字段以符合他们已经在他们自己的数据库中的表,或添加一个表,以匹配在ParentType中映射的。我希望有人会这样做,至少出于好奇心,现在我已经给了一个良好的开始测试。

    解决方案

那么,我至少已经找到了解决问题的办法,但不是为什么。我的解决方案是创建一个新的类型,包含我用作复合id的属性:

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

public virtual long GeneratedId {get;组; }
$ b $ public override bool Equals(object obj)
{
return Equals(obj as CompositeIdType);


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

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

$ b public override int GetHashCode()
{
unchecked
{
int hash = GetType()。GetHashCode();

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

返回散列;





然后,将属性替换为

  public class ParentType 
{$ b $ b public virtual CompositeIdType Key {get;组; }

公共虚拟字符串SomeField {get;组; }
}

通过这些更改,新的映射将为:

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

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

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

Not.LazyLoad();




$ p
$ b

完成所有这些更改后,合并即使在合并调用之前调用获取,也可以使用。我最好的选择是, CompositeId 的非泛型形式没有正确地执行某些操作,或者当您在实体上调用 Merge 时,它所做的映射对NH来说效果不佳使用它(我想进入FNH的来源来解决它,如果是这样的话,但我已经花了太多时间搞清楚如何绕过这个问题)。

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

为了避免这种情况,我可以破解它,以便添加您正在映射和设置的相同类型的引用

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

public virtual ParentType Key {get;组; }

public virtual long AssignedId {get;组; }

public virtual long GeneratedId {get;组; }

公共虚拟字符串SomeField {get;组; }
$ b $ public override bool Equals(object obj)
{
return Equals(obj as ParentType);

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

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

$ b public override int GetHashCode()
{
unchecked
{
int hash = GetType()。GetHashCode();

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

返回散列;





然后映射将是:


  public class ParentMap:ClassMap< ParentType> 
{
public ParentMap()
{
表(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或自引用的新类型)的范围内,因为自我引用对于我的口味来说似乎有点冒险。



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

The project I am working on requires data in our system to be synchronized with that of another (the other system is quite popular which is why synchronization is so important). However, I am having a weird issue when I attempt to update an existing entity that has a composite id.

The problem is that whenever the entity to be updated is retrieved (using Get) prior to calling Merge, it does not work (the changes are not persisted to the DB but no exception is thrown). When I remove the call to Get, updating the entity works. Knowledge on whether the entity exists is needed because if it is being created, part of the composite id needs to be generated.

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 call is made in the ScanForInstance 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 IdResolver is used for determining what should be used for the id (the value of a single key in a mapping, otherwise the object itself for entities with composite ids).

Like I said, if I remove the call to Get it works fine. It works fine for all other operations as well (create, read, and deletes). All operations, including updating, works fine for entities with single keys.


The DB is Pervasive and there are a certain number of restrictions:

  • No, I cannot change any of the schema (I see that as a frequent response to problems with FNB).
  • I do not want to just delete then insert as there are some columns we do not sync back to our system and I do not want to wipe these out

UPDATED: I've added a simple example that people can copy/paste to test this weird behavior (if it is in fact universal). I'm hoping people will do this to at least confirm my problem.

Type to be mapped, Fluent mapping:

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

Don't mind the fact it is called 'ParentType.' I don't actually have any other mappings with this and don't actually use the type as a parent type in this example. It is called this because I'm about to open another question that does involve problems with composite ids and inheritance (DON'T USE COMPOSITE ID'S! :-D).

For the actual testing, I just created a console project in VS with this as the 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>
{

}

I know that adding this sample is only slightly more helpful since anyone wanting to test this would either need to change the ParentType field to conform to a table that they already have in their own DB, or add a table to match what is mapped in ParentType. I'm hoping someone will do this at least out of curiosity now that I've given a good head-start on testing.

解决方案

Well, I've at least figured out a solution to my problem, but not why. My solution was to create a new type that encompassed the properties I was using as the composite 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;
        }
    }
}

Then, substitute the properties in ParentType for a reference to this new type:

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

    public virtual string SomeField { get; set; }
}

With those changes, the new mapping would be:

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

After all these changes are made, Merge works even when Get is called prior to the Merge call. My best bet is the non-generic form of CompositeId is not doing something correctly or that the mapping it is making is not working well with NH when you call Merge on an entity that uses it (I'd like to go into the source of FNH to fix it if that is the case but I've already spent too much time figuring out how to bypass this problem).

This is all well and good but this would require me to create a new type for each entity I am mapping, or at least a new type for an id with a different number of keys (ie a type with 2 keys, a type with 3 keys, etc.).

To avoid this, I can hack it so that you add a reference of the same type you are mapping and set the reference to this in the constructor:

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

Then the mapping would be:

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

I have tested this for updating and inserting using Merge with Get getting called prior to the merge and surprisingly IT WORKS. I'm still on the fence on which fix to use (the new type encompassing the composite id or the self-reference) as the self-reference seems a little to hacky for my tastes.

If anyone finds out WHY this didn't work originally I'd still like to know...

这篇关于当此实体先前已使用Get进行检索时,无法合并具有Composite ID的实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆