【问题标题】:nHibernate enumerating the same collection on multiple threadsnHibernate 在多个线程上枚举同一个集合
【发布时间】:2014-10-23 19:58:33
【问题描述】:

我有一个生产应用程序(IIS8、MVC5、nHibernate DAL),最近我注意到 CPU 使用率很高。循环应用程序池可以修复它,但在从服务器进行一些诊断和内存转储以分析问题后,我注意到多个线程尝试枚举同一个集合的一致模式。最常见的一点是应用程序检查用户的角色。我怀疑这可能更多地是因为这个代码是为每个验证权限的请求运行的,所以它更有可能是它被卡住的集合?

public IList<Role> GetRoles(string username)
{
    var login = GetLoginForUser(username);
    return !login.Groups.Any() ? new List<Role>() : login.Groups.SelectMany(x => x.Roles).OrderBy(x => x.DisplayName).ToList();
}

我的 CurrentUser 对象有一个简单的接口,其中包含从依赖解析器注入的用户详细信息。我已经验证了 UserId 存在且有效,这一切都很简单。当我查看这两个请求何时挂起的转储时,我收到一个警告,即多个线程正在枚举一个集合。当我检查转储中的两个线程时,我看到了几乎相同的堆栈跟踪。 (我已经重命名了堆栈跟踪中的一些命名空间细节,但其他方面没有改变)。两个请求中的 userId(和生成的配置文件)相同,因此看起来这是由于两个单独的线程几乎同时尝试从数据库中加载相同的对象。

堆栈跟踪如下,但我不确定从这里到哪里来解决这个问题。

System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].FindEntry(System.__Canon)+129 
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].TryGetValue(System.__Canon, System.Nullable`1<Int32> ByRef)+12 
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25 
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25 
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e 
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e 
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String)+29 
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16 
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16 
NHibernate.Persister.Collection.AbstractCollectionPersister.ReadKey(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor)+14 
NHibernate.Persister.Collection.AbstractCollectionPersister.ReadKey(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor)+14 
NHibernate.Loader.Loader.ReadCollectionElement(System.Object, System.Object, NHibernate.Persister.Collection.ICollectionPersister, NHibernate.Loader.ICollectionAliases, System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+34 
NHibernate.Loader.Loader.ReadCollectionElement(System.Object, System.Object, NHibernate.Persister.Collection.ICollectionPersister, NHibernate.Loader.ICollectionAliases, System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+34 
NHibernate.Loader.Loader.ReadCollectionElements(System.Object[], System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+d2 
NHibernate.Loader.Loader.ReadCollectionElements(System.Object[], System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+d2 
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+ab 
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+ab 
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7 
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7 
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f 
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f 
NHibernate.Loader.Loader.LoadCollection(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType)+de 
NHibernate.Loader.Loader.LoadCollection(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType)+de 
NHibernate.Loader.Collection.CollectionLoader.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1c 
NHibernate.Loader.Collection.CollectionLoader.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1c 
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1e 
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1e 
NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(NHibernate.Event.InitializeCollectionEvent)+16d 
NHibernate.Impl.SessionImpl.InitializeCollection(NHibernate.Collection.IPersistentCollection, Boolean)+1fa 
NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean)+2f 
NHibernate.Collection.AbstractPersistentCollection.Read()+d 
NHibernate.Collection.Generic.PersistentGenericBag`1[[System.__Canon, mscorlib]].System.Collections.Generic.IEnumerable<T>.GetEnumerator()+11 
System_Core_ni!System.Linq.Enumerable+<SelectManyIterator>d__14`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].MoveNext()+10c 
System_Core_ni!System.Linq.Buffer`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>)+d9 
System_Core_ni!System.Linq.OrderedEnumerable`1+<GetEnumerator>d__0[[System.__Canon, mscorlib]].MoveNext()+6f 
System_Core_ni!System.Linq.OrderedEnumerable`1+<GetEnumerator>d__0[[System.__Canon, mscorlib]].MoveNext()+6f 
mscorlib_ni!System.Collections.Generic.List`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>)+17e 
System_Core_ni!System.Linq.Enumerable.ToList[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>)+3b 
Company.ApplicationServices.SecurityService.GetRoles(System.String)+ef 

我目前在 ActionFilter 中打开我的数据库事务,当OnActionExecuting() 发生时打开事务,然后在OnActionExecuted() 发生时提交/回滚事务。

我正在使用 StructureMap (v2.6.4.1) 进行依赖注入,我的数据持久性的相关行如下。

var cfg = Fluently.Configure()
    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("DatabaseConnectionString"))
    .CurrentSessionContext<WebSessionContext>()
    // ... etc etc....
    .Cache(c => c.ProviderClass<NHibernate.Caches.SysCache2.SysCacheProvider>()
                .UseQueryCache()
                .UseSecondLevelCache()
                .UseMinimalPuts());

For<NHibernate.Cfg.Configuration>().Singleton().Use(cfg);
For<NHibernate.ISessionFactory>().Singleton()
    .Use(ctx =>
        {
            try
            {
                var config = ctx.GetInstance<NHibernate.Cfg.Configuration>();
                return config.BuildSessionFactory();
            }
            catch (SqlException ex)
            {
                ctx.GetInstance<IExceptionLogger>().Error(ex);
                throw;
            }
        });
For<NHibernate.ISession>().HybridHttpOrThreadLocalScoped()
    .Use(ctx => ctx.GetInstance<NHibernate.ISessionFactory>().OpenSession());

更新:我仍在处理这个问题,如果这是 nhibernate 的问题,或者我如何配置它,我希望得到一些提示?由于 19 个单独的线程试图枚举同一个集合,我将应用程序锁定到今天不得不重新启动服务器的地步。

下面提到,SecurityService 的生命周期范围可能存在问题,我同意这是一种可能性。目前,我通过 Structuremap 通过依赖注入提供服务(最新版本 2.6 发布,尚未更新到 3.x)。我在下面简要介绍了这些细节,希望简洁但仍然相关。

public class SecurityService : ISecurityService
{
    private readonly IRepository<Login> loginRepository;

    public IList<Role> GetCurrentUserRoles()
    {
        var login = GetLoginForCurrentUser();
        return GetRoles(login.Name);
    }

    public Login GetLoginForCurrentUser()
    {
        //Some logic to derive the current UserId {guid} via some resources injected into this service class.

        return loginRepository.GetReference(loginId);
    }
}

public class NHibernateRepository<T> : IRepository<T> where T : class
{
    protected ISession Session { get; set; }

    public NHibernateRepository(ISession session)
    {
        Session = session;
    }

    public T GetReference(object id)
    {
        return Session.Get<T>(id);
    }

    // Other methods typical of a repository class, nothing special
}

我的依赖解析器设置....

For<ISecurityService>().Use<SecurityService>();
For(typeof (IRepository<>)).Use(typeof (NHibernateRepository<>));
//And then the ISession is commented above.

nHibernate 配置了 WebSessionContext 的内部上下文 ISessionFactory 是单例的 ISession 是 HybridHttpOrThreadLocalScoped ISecurityService 和 IRepository 都保留为 Transient 的默认值

角色被缓存,如果没有找到,那么系统会调用安全服务上的 GetRoles 方法,我想我可能会遇到问题,它调用 GetRoles 的频率超出了它的需要,但这不在我现在遇到的多个并发枚举问题的范围。

更新: 所以我很困惑,我今天打电话给GetReference也遇到了同样的问题。 18 个单独的线程卡在枚举同一个集合中,但这个是 nHibernate 内部的。

System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].FindEntry(System.__Canon)+129 
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].TryGetValue(System.__Canon, System.Nullable`1 ByRef)+12 
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25 
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e 
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String)+29 
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16 
NHibernate.Type.AbstractType.Hydrate(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+14 
NHibernate.Persister.Entity.AbstractEntityPersister.Hydrate(System.Data.IDataReader, System.Object, System.Object, NHibernate.Persister.Entity.ILoadable, System.String[][], Boolean, NHibernate.Engine.ISessionImplementor)+3ce 
NHibernate.Loader.Loader.LoadFromResultSet(System.Data.IDataReader, Int32, System.Object, System.String, NHibernate.Engine.EntityKey, System.String, NHibernate.LockMode, NHibernate.Persister.Entity.ILoadable, NHibernate.Engine.ISessionImplementor)+118 
NHibernate.Loader.Loader.InstanceNotYetLoaded(System.Data.IDataReader, Int32, NHibernate.Persister.Entity.ILoadable, NHibernate.Engine.EntityKey, NHibernate.LockMode, System.String, NHibernate.Engine.EntityKey, System.Object, System.Collections.IList, NHi+8c 
NHibernate.Loader.Loader.GetRow(System.Data.IDataReader, NHibernate.Persister.Entity.ILoadable[], NHibernate.Engine.EntityKey[], System.Object, NHibernate.Engine.EntityKey, NHibernate.LockMode[], System.Collections.IList, NHibernate.Engine.ISessionImpleme+129 
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+97 
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7 
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f 
NHibernate.Loader.Loader.LoadEntity(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType, System.Object, System.String, System.Object, NHibernate.Persister.Entity.IEntityPersister)+f3 
NHibernate.Loader.Entity.AbstractEntityLoader.Load(NHibernate.Engine.ISessionImplementor, System.Object, System.Object, System.Object)+22 
NHibernate.Loader.Entity.AbstractEntityLoader.Load(System.Object, System.Object, NHibernate.Engine.ISessionImplementor)+12 
NHibernate.Persister.Entity.AbstractEntityPersister.Load(System.Object, System.Object, NHibernate.LockMode, NHibernate.Engine.ISessionImplementor)+69 
NHibernate.Event.Default.DefaultLoadEventListener.LoadFromDatasource(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+84 
NHibernate.Event.Default.DefaultLoadEventListener.DoLoad(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+1d7 
NHibernate.Event.Default.DefaultLoadEventListener.Load(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+5e 
NHibernate.Event.Default.DefaultLoadEventListener.ReturnNarrowedProxy(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType, NHibernate.Engine.IPersistenceContext, System.Object)+73 
NHibernate.Event.Default.DefaultLoadEventListener.ProxyOrLoad(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+cb 
NHibernate.Event.Default.DefaultLoadEventListener.OnLoad(NHibernate.Event.LoadEvent, NHibernate.Event.LoadType)+120 
NHibernate.Impl.SessionImpl.FireLoad(NHibernate.Event.LoadEvent, NHibernate.Event.LoadType)+140 
NHibernate.Impl.SessionImpl.Get(System.String, System.Object)+148 
NHibernate.Impl.SessionImpl.Get(System.Type, System.Object)+121 
NHibernate.Impl.SessionImpl.Get[[System.__Canon, mscorlib]](System.Object)+143 
Intellitive.Data.Repositories.NHibernateRepository`1[[System.__Canon, mscorlib]].GetReference(System.Object)+38

在调用 GetReference 之后还有更多问题,但据我所知,这与问题无关?

【问题讨论】:

  • 您确定在请求 css/js/images 时不会调用 ActionFilter 吗?如果是这种情况,则可能是开销的原因(创建事务并访问数据库以检查每个此类请求的角色)。
  • 我相信是的,我有 MVC 的路由设置来忽略 Content 和 Scripts 路径中的所有内容。并且 nHProf 中没有显示 nhibernate 的记录会话。
  • 您使用的是哪个版本的 NHibernate?

标签: c# asp.net asp.net-mvc multithreading nhibernate


【解决方案1】:

在我看来,您使用的 NHibernate 似乎早于 4.0.0(2014 年 8 月 17 日发布)。如果您使用的是较新版本,请忽略此答案。

NHibernate 存在并发问题 - 请参阅 here

有时我们的 IIS 进程开始使用 100% 的 CPU。在内存转储中 我们看到很多线程在 Dictionary FindEntry 方法中, 从 ColumnNameCache.GetIndexForColumnName 调用。

此问题已解决,但补丁仅合并到版本 4.0.0。

问题是通用Dictionary 在底层集合被修改时进入无限循环,即两个线程正在尝试读取值,一个正在写入。

来自documentation

一个字典可以同时支持多个阅读器, 只要集合没有被修改。即便如此,列举 通过集合本质上不是线程安全的过程。在 枚举与写访问竞争的罕见情况, 在整个枚举过程中必须锁定集合。为了让 集合被多个线程访问以进行读取和写入, 您必须实现自己的同步。

线程不安全版本:https://github.com/nhibernate/nhibernate-core/blob/3.4.x/src/NHibernate/AdoNet/ColumnNameCache.cs

与应用补丁相同:https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/AdoNet/ColumnNameCache.cs

对 Dictionary 为什么不是线程安全的以及为什么 IIS 停止为请求提供服务的详细解释:

  1. http://blogs.msdn.com/b/tess/archive/2009/12/21/high-cpu-in-net-app-using-a-static-generic-dictionary.aspx
  2. http://improve.dk/debugging-in-production-part-2-latent-race-condition-bugs/
  3. ASP.NET Hang - Generic Dictionary concurrency issues causes GC deadlock

【讨论】:

  • 啊,好的。这听起来像是我遇到的问题,并且对GetIndexForColumnName 的调用存在于我的问题中的堆栈跟踪以及我捕获但未发布的其他堆栈跟踪中)。我会尽快将 nHibernate 参考和实现升级到 nHibernate 4.0。
  • 仅供参考,上周我们使用 nHibernate 4.0 完成了站点的迁移/测试,已发布并且到目前为止还没有出现过这个问题。当我上次发布时,我们已经在做这件事了;到目前为止一切顺利,看起来它可以解决问题。 :-)
【解决方案2】:

这个问题有一个罪魁祸首:Company.ApplicationServices.SecurityServicelifetime 会跨越 WebRequest/ISession 生命周期.

一些假设

SecurityService 方法正在执行some magical (上面的问题中没有显示) 调用ISession,接收Login 对象。

如果登录包含组(第一次迭代),它会继续迭代并加载许多角色。

public IList<Role> GetRoles(string username)
{
    // the instance of login is loaded, still referencing some ISession
    var login = GetLoginForUser(username);
    return !login.Groups.Any()  // first iteration over Groups
         ? new List<Role>() 
         // second iteration 
         : login
            .Groups
            .SelectMany(x => x.Roles) // other iterations
            .OrderBy(x => x.DisplayName)
            .ToList();
}

从资源消耗的角度来看,这个调用显然非常昂贵。所以一定有一些缓存的地方(至少保留每个线程的角色,但可能更长)

SecurityService 很可能是单身人士。但这意味着它有自己的ISession。并且这样的 ISession 几乎不会依赖于 Web 请求。这意味着,它可能会一直持续下去。

它可能会返回 same login 实例两个许多 不同 线程 (许多 Web 请求由不同线程处理)

类似的故事:

我建议看看这个问答及其讨论:Nhibernate Lazy Load exception after a view exception。问题,问题原因不同,但解决方案应该是一样的。

建议:

上述假设,即使它们只是部分正确,也应该有助于理解下面的这个建议。简而言之,我们应该避免与其他线程/请求共享来自一个 ISession 的任何对象...

我们可以看到,迭代对象的问题来自one ISession,在other线程内。我喜欢的解决方案:使用Prototype pattern.(模式解决问题昂贵的对象创建/加载)

  • 让我们一次性按用户名加载角色
  • 将它们(原型)克隆到我们需要的关卡中
  • 缓存它们,稍后返回缓存的克隆
  • 引入一些刷新(2 分钟后,sql 缓存依赖,文件依赖),以将安全对象在缓存中保持较短/合理的时间

甚至可能还有其他方法(例如,使用NHibernateUtil.Initialize() 初始化所有属性并跳过克隆)...但我可以确认,克隆对我来说效果很好。

快速概览:

class Group : ICloneable
{
    ...
    public override object Clone()
    {
        var entity = base.Clone() as Group;
        entity.Roles = new List<Role>();
        foreach(var r in Roles)
        {
           entity.Roles.Add(r.Clone() as Role);
        }
        ...
        return entity;
    }
}

class Login: ICloneable
{
    ...
    public override object Clone()
    {
        var entity = base.Clone() as Login;
        entity.Groups = new List<Group>();
        foreach(var g in Group)
        {
           entity.Groups.Add(r.Clone() as Group);
        }
        ...
        return entity;
    }
}

很高兴,上面的草稿在我们手中。我们可以根据需要调整克隆部分。最后我们可以有一个克隆,加载一次,独立于任何会话,只有安全所需的属性......准备缓存

EXTEND:基于扩展问题中的更多信息

我想说,与我上面的猜测有关,有罪魁祸首(至少怀疑)

角色被缓存...

但这些角色与会话相关。他们没有分离。他们刚刚通过 Linq 在 ISession 返回的 Login 实例之上收到:

login.Groups.SelectMany(x => x.Roles).OrderBy(x => x.DisplayName).ToList()

这些对象(登录、组、角色)中的每一个仍然附加到它所诞生的会话。

与此同时,不同的网络请求即将到来。同一登录的不同 Web 请求(部分视图、Web API 调用)。因此,在多线程环境中,更多的 Web 请求可以接触到缓存的角色并使用它们。这些仍然与现有的、打开的 ISession 相关,但在不同的线程上

您的框架很可能大量使用这些来决定显示什么、隐藏什么、编辑什么...

所以在许多网络请求中,有一个共享对象(对象集)与另一个 ISession 相关。

我的建议:分离这些对象。我发现更准确的方法是原型模式。

所以,我相信这应该让您了解这些问题是如何发生的,但我想强调的是解决方案。不要在多个请求/线程之间共享与一个会话相关的对象。克隆只是一种方式。但校长是一把钥匙。

【讨论】:

  • 我在上面添加了一些详细信息,说明我对您提到的大多数方面的范围界定。我没有看到我目前如何配置它的问题,但我离问题太近了,我不确定如果有的话我会看到它,显然某处有问题,否则我不会有我目前的困境:-P
  • 克隆可能是一种选择,但我想尝试找出为什么要为不同线程/请求上的多个请求传回相同的实例。从堆栈跟踪的外观来看,是 .ToList() 导致问题,因为我的 SelectMany() 的结果是同一个集合,但我怀疑这个结果因范围界定而因每个线程/请求而不同在 ISession 上,还是在内部缓存结果并返回相同的集合?
  • 在我看来,最关键的是解决方案。我试图在我的扩展中提供更多细节,但简而言之:分离您放置在缓存中的任何对象。我的方法是使用克隆...
  • 我添加了对我的堆栈跟踪的第二次出现的修正,该修正在不同的数据访问调用上似乎是相同的问题;从我的业务逻辑的角度来看,它与集合无关。我知道你来自哪里,但我的错误似乎来自 nhibernate 内部,因为只是试图访问数据。如果尝试检索数据的行为是导致错误的原因,那么无论我是直接执行还是首先克隆它,都会改变我与该数据交互的方式的行为会有什么影响?
  • 我也遇到了困难,因为我正在从 nHibernate 3.x 升级到 4.x,希望它可能会有所帮助,所以我的生产副本目前与我的开发代码不匹配。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-22
相关资源
最近更新 更多