Проект, над которым я работаю, требует, чтобы данные в нашей системе были синхронизированы с данными другой (другая система довольно популярна, поэтому синхронизация так важна). Однако у меня возникает странная проблема, когда я пытаюсь обновить существующую сущность с составным идентификатором.
Проблема в том, что всякий раз, когда обновляемый объект извлекается (используя Get ) до вызова Merge , он не работает (изменения не сохраняются в БД, но исключение не выбрасывается). Когда я удаляю вызов Get , обновление сущности работает. Знание того, существует ли сущность, необходимо, потому что, если она создается, необходимо создать часть составного идентификатора.
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();
}
Вызов Get выполняется в методе 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 используется для определения того, что следует использовать для идентификатора (значение одного ключа в отображении, в противном случае сам объект для сущностей с составными идентификаторами).
Как я уже сказал, если я уберу вызов Get , он будет работать нормально. Он отлично работает и для всех других операций (создание, чтение и удаление). Все операции, включая обновление, отлично работает для сущностей с одним ключом.
База данных является распространяющейся и существует определенное количество ограничений:
- Нет, я не могу изменить какую-либо схему (я вижу это как частый ответ на проблемы с FNB).
- Я не хочу просто удалять, а затем вставлять, поскольку есть некоторые столбцы, которые мы не синхронизируем с нашей системой, и я не хочу стирать их
ОБНОВЛЕНО: Я добавил простой пример, когда люди могут копировать / вставлять, чтобы проверить это странное поведение (если оно действительно универсально). Я надеюсь, что люди сделают это, чтобы хотя бы подтвердить мою проблему.
Тип для отображения, Отображение по флюенту:
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». На самом деле у меня нет других сопоставлений с этим, и я на самом деле не использую тип в качестве родительского типа в этом примере. Это называется так, потому что я собираюсь открыть еще один вопрос, который связан с проблемами с составными идентификаторами и наследованием ( НЕ ИСПОЛЬЗУЙТЕ КОМПОЗИЦИОННЫЙ ИД!! -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. Я надеюсь, что кто-то сделает это, по крайней мере, из любопытства, когда я дал хороший старт тестированию.