好吧,首先我想说我正在为我的项目使用NHibernate,在这个项目中我们有(除其他外)一个同步功能(从中央MSSQL数据库同步到本地SQLite).现在我知道NHibernate并不是为了同步数据库,但我还想这样做.
我有一个中型大型数据库模型,所以我不能在这里添加它,但问题是我有两个数据表,一个链接表将它们都链接起来.
数据库模型:
| Product | | ProductLinkProducer | | Producer |
|--------------------| |---------------------| |---------------------|
| Id | | LinkId | | Id |
| Name | | Product | | Name |
| ProductLinkProducer| | Producer | | ProductLinkProducer |
数据库:
| Product | | ProductLinkProducer | | Producer |
|---------| |---------------------| |----------|
| Id | | LinkId | | Id |
| Name | | ProductId | | Name |
| | | ProducerId | | |
因此,在同步期间,我首先从Product表中复制所有数据,然后从Producer表复制(基本上是var products = session.Query< Products>().ToList()).这是由NHibernate在一个语句中完成的:
select
product0_.id as id2_,
product0_.name as name2_
from
Product product0_
现在我必须从第一个会话中逐出所有项目(products.ForEach(x => session.Evict(x));)
然后save(products.ForEach(x => syncSession.save(x));)是每行一个插入(如预期的那样).
因此,当在链接表中保存数据时,我希望也只有一个选择.然而事实并非如此.因为首先它选择……如上所述.但现在在每行插入之前,它会为产品和生产者做更多的选择.
所以它看起来像:
产品介绍:
>选择
> insert(id 1)
>插入(id 2)
制片人:
>选择
>插入(id 101)
>插入(id 102)
ProdLinkProducer:
>选择
>从产品中选择ID 1
>从产品中选择ID 1
>从Producer中选择id 101
>从产品中选择ID 2
>从产品中选择ID 2
>从Producer中选择id 102
>从Producer中选择id 102
>插入
>插入
那么有没有避免这种行为?
编辑
为了更好地解释我所做的事情,我创建了一个小型测试项目.它可以在这里找到:https://github.com/tb2johm/NHibernateSync
(我本来希望只添加一个ghist,但我认为它可能遗漏了很多数据,对不起…)
EDIT2
我找到了一种让它起作用的方法,但我不喜欢它.
此解决方案的工作方式是在数据库模型中创建ProductLinkProducerSync表,该表不包含任何链接,只包含值,并避免同步普通链接表,而只是“同步”表.但正如我所说,我不喜欢这个想法,因为如果我在数据库中更改任何内容,我在两个地方需要更新相同的数据.
最佳答案 我无法找到NHibernate开箱即用的方式来做你想问的事.
然而,通过手动将FK引用(代理类)重新绑定到新会话,我能够获得所需的行为(我猜想有什么比没有好的东西:):
var links = session.Query<ProductLinkProducer>().ToList();
links.ForEach(x => session.Evict(x));
foreach (var link in links)
{
link.Product = syncSession.Get<Product>(link.Product.Id);
link.Producer = syncSession.Get<Producer>(link.Producer.Id);
syncSession.Save(link);
}
syncSession.Flush();
这是使用NHibernate元数据服务的通用版本:
static IEnumerable<Action<ISession, T>> GetRefBindActions<T>(ISessionFactory sessionFactory)
{
var classMeta = sessionFactory.GetClassMetadata(typeof(T));
var propertyNames = classMeta.PropertyNames;
var propertyTypes = classMeta.PropertyTypes;
for (int i = 0; i < propertyTypes.Length; i++)
{
var propertyType = propertyTypes[i];
if (propertyType.IsAssociationType && !propertyType.IsCollectionType)
{
var propertyName = propertyNames[i];
var propertyClass = propertyType.ReturnedClass;
var propertyClassMeta = sessionFactory.GetClassMetadata(propertyClass);
yield return (session, target) =>
{
var oldValue = classMeta.GetPropertyValue(target, propertyName, EntityMode.Poco);
var id = propertyClassMeta.GetIdentifier(oldValue, EntityMode.Poco);
var newValue = session.Get(propertyClass, id);
classMeta.SetPropertyValue(target, propertyName, newValue, EntityMode.Poco);
};
}
}
}
并将其应用于您的Sync方法:
private static void Sync<T>(string tableName, ISession session, ISession syncSession)
{
Console.WriteLine("Fetching data for ####{0}####...", tableName);
var sqlLinks = session.Query<T>();
var links = sqlLinks.ToList();
Console.WriteLine("...Done");
Console.WriteLine("Evicting data...");
links.ForEach(x => session.Evict(x));
Console.WriteLine("...Done");
Console.WriteLine("Saving data...");
var bindRefs = GetRefBindActions<T>(syncSession.SessionFactory).ToList();
foreach (var link in links)
{
foreach (var action in bindRefs) action(syncSession, link);
syncSession.Save(link);
}
Console.WriteLine("...Flushing data...");
syncSession.Flush();
Console.WriteLine("...Done");
Console.WriteLine("\n\n\n");
}