每个请求使用一个会话,如何处理更新子对象

时间:2013-04-06 20:06:23

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

在尝试修改子对象然后保存父对象时,我在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,我会收到该异常。
  3. 仅供参考,我尝试替换SaveOrUpdate()存储库中的Merge()。它做得非常出色,但在使用Merge()创建对象时,我的对象永远不会被设置为Id。

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

    我迷路了,希望得到一些帮助。对我的存储库的任何建议也是受欢迎的,因为我对此很新。

    谢谢你们!

2 个答案:

答案 0 :(得分:1)

这个StackOverflow Answer给出了使用合并的绝佳描述。

但是...

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

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

另外,请确保使用SessionFactory.OpenSession()而不是使用SessionFactory.GetCurrentSession()来尝试会话,这样可以使用NHibernate的onus来返回当前正确的会话。

我希望这会把你推向正确的方向。

答案 1 :(得分:1)

此代码

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

正在创建一个新的Employee对象,可以通过构造函数传递ID为1。您应该从数据库加载Employee,并且您的Employee对象不应该允许设置ID,因为您使用的是标识列。此外,新员工不会有任何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();
}