No se puede fusionar una entidad con un Id. Compuesto cuando esa entidad se haya recuperado previamente con Get

El proyecto en el que estoy trabajando requiere que los datos de nuestro sistema se sincronicen con los de otro (el otro sistema es bastante popular, por eso la sincronización es tan importante). Sin embargo, tengo un problema extraño cuando intento actualizar una entidad existente que tiene una identificación compuesta.

El problema es que siempre que se recupera la entidad que se va a actualizar (usandoObtene) antes de llamar aUni, no funciona (los cambios no se mantienen en la base de datos pero no se produce ninguna excepción). Cuando elimino la llamada aObtene, la actualización de la entidad funciona. Se necesita saber si la entidad existe porque si se está creando, se debe generar parte de la identificación compuesta.

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

LosObtenea llamada @ se realiza en la ScanForInstance método:

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

Los IdResolver se usa para determinar qué se debe usar para la identificación (el valor de una sola clave en una asignación, de lo contrario, el objeto en sí para entidades con identificadores compuestos).

Como dije, si elimino la llamada aObtene funciona bien. Funciona bien para todas las demás operaciones también (crear, leer y eliminar). Todas las operaciones, incluida la actualización, funcionan bien para entidades con claves únicas.

La base de datos es generalizada y existen ciertas restricciones:

No, no puedo cambiar ninguno de los esquemas (lo veo como una respuesta frecuente a problemas con FNB). No quiero simplemente eliminar y luego insertar, ya que hay algunas columnas que no sincronizamos de nuevo con nuestro sistema y no quiero borrarlas

ACTUALIZADO He agregado un ejemplo simple de que las personas pueden copiar / pegar para probar este comportamiento extraño (si de hecho es universal). Espero que la gente haga esto para al menos confirmar mi problema.

Tipo a mapear, Mapeo fluido:

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

No importa el hecho de que se llama 'ParentType'. En realidad, no tengo ninguna otra asignación con esto y en realidad no uso el tipo como tipo primario en este ejemplo. Se llama así porque estoy a punto de abrir otra pregunta que implica problemas con los identificadores compuestos y la herencia ¡NO UTILICE ID DE COMPUESTO! :-R).

Para la prueba real, acabo de crear un proyecto de consola en VS con esto como 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>
{

}

Sé que agregar esta muestra es solo un poco más útil, ya que cualquiera que quiera probar esto necesitaría cambiar el campo ParentType para ajustarse a una tabla que ya tienen en su propia base de datos, o agregar una tabla para que coincida con lo que está asignado ParentType. Espero que alguien haga esto al menos por curiosidad ahora que he dado un buen comienzo en las pruebas.

Respuestas a la pregunta(1)

Su respuesta a la pregunta