【问题标题】:Does NHibernate really deliver transparent persistencyNHibernate 真的提供透明的持久性吗
【发布时间】:2011-08-24 11:35:43
【问题描述】:

开始使用 Nhibernate 来实现持久性,被它尊重您的域模型的承诺所吸引,我尝试为我的域对象实现一个关系管理器。基本上,为了在管理双向一对多和多对多关系方面干燥我的代码,我决定让这些关系由一个单独的类管理。当设置一对多或多对一属性时,在字典中创建两个对象的条目,键要么是带有集合值的一侧,以保存多侧,要么是带有值的多侧一侧。

特定类型组合的一对多关系如下所示:

public class OneToManyRelation<TOnePart, TManyPart> : IRelation<IRelationPart, IRelationPart>
    where TOnePart : class, IRelationPart
    where TManyPart : class, IRelationPart
{
    private readonly IDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>> _oneToMany;
    private readonly IDictionary<TManyPart, TOnePart> _manyToOne;

    public OneToManyRelation()
    {
        _manyToOne = new ConcurrentDictionary<TManyPart, TOnePart>();
        _oneToMany = new ConcurrentDictionary<TOnePart, Iesi.Collections.Generic.ISet<TManyPart>>();
    }

    public void Set(TOnePart onePart, TManyPart manyPart)
    {
        if (onePart == null || manyPart == null) return;
        if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
        else _manyToOne[manyPart] = onePart;
    }

    public void Add(TOnePart onePart, TManyPart manyPart)
    {
        if (onePart == null || manyPart == null) return;

        if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
        else _manyToOne[manyPart] = onePart;
        if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
        _oneToMany[onePart].Add(manyPart);
    }

    public Iesi.Collections.Generic.ISet<TManyPart> GetManyPart(TOnePart onePart)
    {
        if (!_oneToMany.ContainsKey(onePart)) _oneToMany[onePart] = new HashedSet<TManyPart>();
        return _oneToMany[onePart];
    }

    public TOnePart GetOnePart(TManyPart manyPart)
    {
        if(!_manyToOne.ContainsKey(manyPart)) _manyToOne[manyPart] = default(TOnePart);
        return _manyToOne[manyPart];
    }

    public void Remove(TOnePart onePart, TManyPart manyPart)
    {
        _manyToOne.Remove(manyPart);
        _oneToMany[onePart].Remove(manyPart);
    }

    public void Set(TOnePart onePart, Iesi.Collections.Generic.ISet<TManyPart> manyPart)
    {
        if (onePart == null) return;
        if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, manyPart);
        else _oneToMany[onePart] = manyPart;
    }

    public void Clear(TOnePart onePart)
    {
        var list = new HashedSet<TManyPart>(_oneToMany[onePart]);
        foreach (var manyPart in list)
        {
            _manyToOne.Remove(manyPart);
        }
        _oneToMany.Remove(onePart);
    }

    public void Clear(TManyPart manyPart)
    {
        if (!_manyToOne.ContainsKey(manyPart)) return;
        if (_manyToOne[manyPart] == null) return;

        _oneToMany[_manyToOne[manyPart]].Remove(manyPart);
        _manyToOne.Remove(manyPart);
    }
}

在许多方面,代码 sn-p 看起来像:

    public virtual SubstanceGroup SubstanceGroup
    {
        get { return RelationProvider.SubstanceGroupSubstance.GetOnePart(this); } 
        protected set { RelationProvider.SubstanceGroupSubstance.Set(value, this); }
    }

一方面,在这种情况下,SubstanceGroup,sn-p 看起来像:

    public virtual ISet<Substance> Substances
    {
        get { return RelationProvider.SubstanceGroupSubstance.GetManyPart(this); }
        protected set { RelationProvider.SubstanceGroupSubstance.Set(this, value); }
    }

仅使用我的域对象,这非常有效。在域对象中,我只需要引用一个抽象工厂来检索适当的关系,我可以从一侧设置关系,从而自动成为双向的。

但是,当 NH 出现问题时,我的字典中有重复的键。 NH 以某种方式设置了一个具有空值(!)的关系属性和一个域对象的新副本(?)。因此,当域对象被保存时,我有该域对象的两个条目,例如关系的多方面,即 _manyToOne 字典。

这个问题让我脱发,我不明白这是怎么回事??

【问题讨论】:

  • 我不太明白你在做什么。这个关系类是您在实体中用作字段的类型吗?它是如何映射的?您如何创建、读取和更改数据?
  • 我的问题不完整,我已经编辑并添加了一个代码 sn-p 我如何使用这个关系类。顺便说一句,关系类表示两个对象之间的关系,就是这样。在一对多关系的情况下,有一个部分和多个部分。
  • 似乎是一种管理双向关系的复杂方式。似乎此解决方案存在问题。我认为使用添加/删除方法手动管理实体内部的这些关系会更容易。
  • 我听从了您的建议,并恢复到内部私有字段来存储对父对象和子对象的引用。确实,这个问题似乎已经解决了。但是,我仍然很困惑为什么上述解决方案不起作用,不管是否复杂(虽然确实为我节省了大约 500 行代码)。

标签: nhibernate bidirectional-relation


【解决方案1】:

要回答您的第一个非常笼统的问题:“NHibernate 真的提供透明的持久性”,我只能说:没有什么是完美的。 NH 尽可能保持透明,同时尽可能降低复杂性。

有一些假设,特别是关于集合:集合及其实现不被视为您的域模型的一部分。 NH 提供了自己的集合实现。您不仅需要使用ISetIList 等接口。当从数据库中读取对象时,您还应该采用 NH 给出的实例,并且永远不要用您自己的实例替换它。 (我不知道你的关系类实际上是用来做什么的,所以我不知道这是否是这里的问题。)

域对象在同一会话实例中是唯一的。如果您每次都获得域对象的新实例,那么您可能实现了“session-per-call”反模式,它为每次数据库交互创建一个新会话。

我不知道你到底在做什么。这个 OneToManyRelation 实际上是如何使用的?当 NH 表现不如预期时,你在做什么?对于您的具体实施,这是一个非常具体的问题。

【讨论】:

  • 感谢您努力理解我的代码。关于您关于集合不被视为域模型的一部分的评论,我非常不同意,但我已经知道 NH 是如何处理这个问题的。因此,我恭敬地接受来自 NH 的所有对集合的引用,并将它们存储在我的 Relation 类中,因为您可以看到许多部分是 Iesi.Collections.Generic.ISet。此外,我不使用会话每次调用反模式,但我将会话视为一个工作单元。工作完成后,会话就消失了,我的领域模型也消失了,客户端被告知了结果。
  • 最后,我不认为在领域模型中管理一对多关系和多对多关系是非常具体的问题。我刚刚注意到,用特殊的 set 和 get 方法处理域类中的本地字段,这会导致重复的重复代码。如果父母有一个孩子并且孩子应该知道它是父母,那么双方都应该有一些代码。而且我的代码中有很多父子关系。以及多对多关系。
  • IMO,这个重复的代码已经够琐碎的了。有时,如果代码很简单,即使某些模式是重复的,生活也会更容易——与创建实现这些模式的魔术类相比。
  • 关于你的观点,集合和实现不被视为域模型的一部分:我错了!没明白,但现在我想我明白了:stackoverflow.com/questions/7230158/…
【解决方案2】:

除了关于“复杂代码”和“你到底在做什么”的 cmets。问题是我正在替换 NH 的持久性集合,如下面的代码 sn-p:

public void Add(TOnePart onePart, TManyPart manyPart)
{
    if (onePart == null || manyPart == null) return;

    if (!_manyToOne.ContainsKey(manyPart)) _manyToOne.Add(manyPart, onePart);
    else _manyToOne[manyPart] = onePart;
    if (!_oneToMany.ContainsKey(onePart)) _oneToMany.Add(onePart, new HashedSet<TManyPart>());
    _oneToMany[onePart].Add(manyPart);
}

我为许多部分创建了一个新的哈希集。这就是问题所在。如果只是设置了传入集合的许多部分(在 NH 的持久性集合实现的情况下),那么它会起作用。

作为 NH 新手,用 NH 的特殊实现替换集合是一个重要的错误来源。就像对其他 NH 新手的警告一样。

【讨论】:

  • 顺便说一句,请阅读 Stefan Steinegger 的一篇关于邪恶 Dry 的有趣帖子。不确定我的关系经理是不是那么邪恶,但仍然值得考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
  • 1970-01-01
  • 2021-04-04
  • 2014-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多