ORM 和对象身份存在一个众所周知的问题。就 ORM 而言,如果实体具有相同的 ID,则它们是平等的。当然,这不适用于被认为不存在的 transient 实例。
但就 OO 代码而言,如果对象引用引用同一实例,则它们被认为是相等的。也就是说,除非 Equals
和/或 ==
被覆盖。
这一切都很好,但这在实践中意味着什么?这是一个非常简单的域模型示例:
namespace TryHibernate.Example
{
public abstract class Entity
{
public int Id { get; set; }
}
public class Employee : Entity
{
public string Name { get; set; }
public IList<Task> Tasks { get; set; }
public Employee()
{
Tasks = new List<Task>();
}
}
public class Task : Entity
{
public Employee Assignee { get; set; }
public Job Job { get; set; }
}
public class Job : Entity
{
public string Description { get; set; }
}
} // namespace
下面是使用它的示例代码:
using (ISessionFactory sessionFactory = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql())
//.Cache(c => c.UseSecondLevelCache().UseQueryCache().ProviderClass<HashtableCacheProvider>())
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Entity>()
.Where(type => type.Namespace == typeof(Entity).Namespace)
.Conventions.Add(DefaultLazy.Never())
.Conventions.Add(DefaultCascade.None())
//.Conventions.Add(ConventionBuilder.Class.Always(c => c.Cache.ReadWrite()))
).ExportTo("hbm")
).ExposeConfiguration(c => new SchemaExport(c).Create(true, true))
.BuildSessionFactory())
{
Job job = new Job() { Description = "A very important job" };
Employee empl = new Employee() { Name = "John Smith" };
Task task = new Task() { Job = job, Assignee = empl };
using (ISession db = sessionFactory.OpenSession())
using (ITransaction t = db.BeginTransaction())
{
db.Save(job);
db.Save(empl);
empl.Tasks.Add(task);
db.Save(task);
t.Commit();
}
IList<Job> jobs;
using (ISession db = sessionFactory.OpenSession())
{
jobs = db.QueryOver<Job>().List();
}
IList<Employee> employees;
using (ISession db = sessionFactory.OpenSession())
{
employees = db.QueryOver<Employee>().List();
}
jobs[0].Description = "A totally unimportant job";
Console.WriteLine(employees[0].Tasks[0].Job.Description);
}
当然,它会打印“A very important job”。启用二级缓存(注释掉)不会改变它,尽管它在某些情况下会减少数据库命中率。显然这是因为 NHibernate 缓存数据,而不是对象实例。
当然,覆盖相等性/哈希码在这里没有帮助,因为引起问题的不是相等性。事实上,我这里有两个相同的实例。
这一切都很好,但如何处理呢?有多种选择,但似乎都不太吸引我:
引入一个中间服务层,它将实例缓存在哈希表中,并在从存储库加载实例后遍历实体图。我不太喜欢它,因为它需要大量工作,容易出错,而且听起来像是我在做 ORM 的工作。如果我想这样做,我宁愿手动实现整个持久性,但我没有。
引入单个聚合根并将其从数据库中拉出一次,而不是获取多个部分。它可以工作,因为我的应用程序相对简单,我可以处理整个图形。事实上,无论如何我都在使用它。但我不喜欢这样,因为它引入了不必要的实体。工作应该是工作,员工应该是员工。当然,我可以将神实体命名为“组织”之类的。我不喜欢它的另一个原因是,如果将来数据增长,它会变得笨拙。例如,我可能希望归档旧的工作和任务。
对所有事情使用单个 session 。现在,我正在打开和关闭 session ,因为我需要加载/存储一些东西。我可以使用单个 session ,并且 NHibernate 的身份映射将保证引用身份(只要我不使用延迟加载)。这似乎是最佳选择,但应用程序可能会运行一段时间(它是 WPF 桌面应用程序),我不喜欢让 session 打开太久的想法。
手动更新所有实例。例如,如果我想更改职位描述,我会调用一些服务方法来搜索具有相同 ID 的职位实例并更新它们。这可能会变得非常困惑,因为该服务必须能够访问基本上所有的东西,本质上成为一种上帝服务。
还有其他我错过的选择吗?令我惊讶的是关于这个问题的信息很少。我能找到的最好的是 this post , 但它只处理平等问题。
最佳答案
好吧,这就是我想出的。基本上,这是经过一些调整的选项 3。我的 repo 类现在看起来像这样:
public class Repo : IDisposable
{
private ISessionFactory sessionFactory;
private ISession session;
public Repo(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
this.session = sessionFactory.OpenSession();
session.Disconnect();
}
public void Dispose()
{
try
{
session.Dispose();
}
finally
{
sessionFactory.Dispose();
}
}
public void Save(Entity entity)
{
using (Connection connection = new Connection(session))
using (ITransaction t = session.BeginTransaction())
{
session.Save(entity);
t.Commit();
}
}
public IList<T> GetList<T>() where T : Entity
{
using (Connection connection = new Connection(session))
{
return session.QueryOver<T>().List();
}
}
private class Connection : IDisposable
{
private ISession session;
internal Connection(ISession session)
{
this.session = session;
session.Reconnect();
}
public void Dispose()
{
session.Disconnect();
}
}
}
它是这样使用的:
using (Repo db = new Repo(sessionFactory))
{
Job job = new Job() { Description = "A very important job" };
Employee empl = new Employee() { Name = "John Smith" };
Task task = new Task() { Job = job, Assignee = empl };
db.Save(job);
db.Save(empl);
empl.Tasks.Add(task);
db.Save(task);
IList<Job> jobs;
jobs = db.GetList<Job>();
IList<Employee> employees;
employees = db.GetList<Employee>();
jobs[0].Description = "A totally unimportant job";
Console.WriteLine(employees[0].Tasks[0].Job.Description);
}
不要在意缺少交易,当然在现实生活中它比这复杂一点,我只是忽略了不相关的部分。重要的是它可以正常工作,并且不会无限期地保持打开的连接。
不过,我还是不喜欢这个。看起来我在这里滥用了 ORM,方法 Disconnect()
和 Reconnect()
对我来说看起来像是 hack。但其他选项看起来一点吸引力都没有。
Callum 的建议是在一个 session 中加载所有内容,然后在另一个 session 中保存,这要好得多,但它不适合我的特定应用程序。要弄清楚发生了什么变化太复杂了。我可以通过使用命令和/或工作单元模式来解决这个问题。使用命令模式还可以让我很好地撤消更改。但我还不愿意走那么远。
关于c# - 使用NHibernate时如何处理不同的实体实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38866876/