【问题标题】:Using one session per request, how to handle updating child objects每个请求使用一个会话,如何处理更新子对象
【发布时间】:2013-04-06 20:06:23
【问题描述】:

在尝试修改子对象然后保存父对象时,我的 ASP.NET WebForms 应用程序中的 Fluent Nhibernate 遇到了一些严重问题。

我的解决方案目前由 2 个项目组成:

  • 核心:所有实体和存储库类都位于其中的类库
  • 网站:ASP.NET 4.5 WebForms 应用程序

这是我的 Employee 对象的简单映射:

public class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.DateCreated);
        Map(x => x.Username);
        Map(x => x.FirstName);
        Map(x => x.LastName);
        HasMany(x => x.TimeEntries).Inverse().Cascade.All().KeyColumn("Employee_id");
    }
}

这是我对 TimeEntry 对象的映射:

public class TimeEntryMap : ClassMap<TimeEntry>
{
    public TimeEntryMap()
    {
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.DateCreated);
        Map(x => x.Date);
        Map(x => x.Length);
        References(x => x.Employee).Column("Employee_id").Not.Nullable();
    }
}

如标题所述,我在我的网络应用程序中使用每个请求一个会话,在 Gobal.asax 中使用此代码:

    public static ISessionFactory SessionFactory = Core.SessionFactoryManager.CreateSessionFactory();

    public static ISession CurrentSession
    {
        get { return (ISession)HttpContext.Current.Items["current.session"]; }
        set { HttpContext.Current.Items["current.session"] = value; }
    }

    protected Global()
    {
        BeginRequest += delegate
        {
            System.Diagnostics.Debug.WriteLine("New Session");
            CurrentSession = SessionFactory.OpenSession();
        };
        EndRequest += delegate
        {
            if (CurrentSession != null)
                CurrentSession.Dispose();
        };
    } 

另外,这是我的 SessionFactoryManager 类:

public class SessionFactoryManager
{
    public static ISession CurrentSession;

    public static ISessionFactory CreateSessionFactory()
    {
        return Fluently.Configure()
        .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Website.Properties.Settings.WebSiteConnString")))
        .Mappings(m => m
        .FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
        .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
        .BuildSessionFactory();
    }

    public static ISession GetSession()
    {
        return (ISession)HttpContext.Current.Items["current.session"];
    }
}

这是我的存储库类之一,用于处理 Employee 的对象数据操作:

public class EmployeeRepository<T> : IRepository<T> where T : Employee
{
    private readonly ISession _session;

    public EmployeeRepository(ISession session)
    {
        _session = session;
    }

    public T GetById(int id)
    {
        T result = null;
        using (ITransaction tx = _session.BeginTransaction())
        {
            result = _session.Get<T>(id);
            tx.Commit();
        }
        return result;
    }

    public IList<T> GetAll()
    {
        IList<T> result = null;
        using (ITransaction tx = _session.BeginTransaction())
        {
            result = _session.Query<T>().ToList();
            tx.Commit();
        }
        return result;
    }

    public bool Save(T item)
    {
        var result = false;
        using (ITransaction tx = _session.BeginTransaction())
        {
            _session.SaveOrUpdate(item);
            tx.Commit();
            result = true;
        }
        return result;
    }

    public bool Delete(T item)
    {
        var result = false;
        using (ITransaction tx = _session.BeginTransaction())
        {
            _session.Delete(_session.Load(typeof (T), item.Id));
            tx.Commit();
            result = true;
        }
        return result;
    }

    public int Count()
    {
        var result = 0;
        using (ITransaction tx = _session.BeginTransaction())
        {
            result = _session.Query<T>().Count();
            tx.Commit();
        }
        return result;
    }
}

现在,这是我的问题。当我尝试插入员工时,一切都很好。更新也是完美的……好吧,只要我不更新 Employee 的“TimeEntries”属性中引用的 TimeEntry 对象之一……

这里是引发异常的地方(在 Web 项目的 ASPX 文件中):

            var emp = new Employee(1);
            foreach (var timeEntry in emp.TimeEntries)
            {
                timeEntry.Length += 1;
            }
            emp.Save();

这是引发的异常:

[NonUniqueObjectException:具有相同标识符的不同对象 值已与会话相关联:1,实体: Core.Entities.Employee]

基本上,每当我尝试

  1. 加载员工并
  2. 修改一个已保存的TimeEntry,我得到了那个异常。

仅供参考,我尝试将存储库中的SaveOrUpdate() 替换为Merge()。它做得很好,但是当使用 Merge() 创建对象时,我的对象永远不会设置它的 ID。

我还尝试在我的存储库的每个函数中创建和刷新ISession。这是没有意义的,因为当我尝试加载 Employee 的 TimeEntries 属性时,就会引发异常,说由于 ISession 已关闭,该对象无法延迟加载...

我迷路了,希望能得到一些帮助。也欢迎对我的存储库提出任何建议,因为我对此很陌生。

谢谢你们!

【问题讨论】:

  • 先加载对象,再更新。你不会得到那个例外。
  • @DarthVader 我修改了我的员工存储库以反映您的建议:public T GetById(int id) { T result = null; using (ITransaction tx = _session.BeginTransaction()) { //result = _session.Get&lt;T&gt;(id); result = _session.Load&lt;T&gt;(id); tx.Commit(); } return result; } 不幸的是,发生了同样的错误。也许我误解了您的评论:您是否建议我手动加载 List 属性中的每个对象?
  • 好吧,正如错误所说,您已经在会话中获得了具有相同 ID 值的不同对象。我会在请求开始时设置断点,并在CurrentSession 上进行监视,以确定设置“不同对象”的位置和时间。
  • @Snixtor 集合中的对象在我的问题的最后一个sn-p中被修改。我想了解为什么我不能修改其中一个子对象,然后保存父对象以传播并提交所有更改。我认为这就是 .Cascade.All() 属性的用途。

标签: asp.net nhibernate fluent-nhibernate repository webforms


【解决方案1】:

这段代码

    var emp = new Employee(1);
    foreach (var timeEntry in emp.TimeEntries)
    {
        timeEntry.Length += 1;
    }
    emp.Save();

正在创建一个新的 Employee 对象,推测其 ID 为 1,并通过构造函数传递。您应该从数据库中加载 Employee,并且您的 Employee 对象不应允许设置 ID,因为您使用的是标识列。此外,新 Employee 不会有任何 TimeEntries,错误消息清楚地指出 Employee 实例是问题所在。

我不喜欢存储库中的事务,我真的不喜欢通用存储库。为什么您的 EmployeeRepository 是通用的?不应该吗

public class EmployeeRepository : IRepository<Employee>

我认为您的代码应该类似于:

var repository = new EmployeeRepository(session);
var emp = repository.GetById(1);
foreach (var timeEntry in emp.TimeEntries)
{
    timeEntry.Length += 1;
}
repository.Save(emp);

我个人更喜欢直接使用 ISession:

using (var txn = _session.BeginTransaction())
{
    var emp = _session.Get<Employee>(1);
    foreach (var timeEntry in emp.TimeEntries)
    {
        timeEntry.Length += 1;
    }
    txn.Commit();
}

【讨论】:

  • 谢谢!!!通过按照您建议的方式更改我的存储库实现(从 ASPX 页面调用存储库),它可以完美运行!我想我的错误是通过将 id 传递给构造函数来创建一个新用户(然后从对象构造函数中调用 repo,我手动将所有属性复制到当前对象),然后期望这个对象被视为一个来自回购......长话短说:使用回购返回的对象!谢谢! :)
【解决方案2】:

这个StackOverflow Answer 很好地描述了使用合并。

但是……

我相信您在为您的应用程序设置正确的会话模式时遇到了问题。

我建议你看看 session-per-request 模式 您在哪里为每个请求创建一个 NHibernate 会话对象。会话在收到请求时打开,并在生成响应时关闭/刷新。

还要确保不要使用SessionFactory.OpenSession() 来获取会话,而是尝试使用SessionFactory.GetCurrentSession(),这会使NHibernate 有责任返回当前正确的会话。

我希望这能将你推向正确的方向。

【讨论】:

    猜你喜欢
    • 2020-02-19
    • 2021-10-20
    • 2019-01-16
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    • 2016-08-07
    • 2011-05-15
    • 2015-04-12
    相关资源
    最近更新 更多