Hello World
Spiga

NHibernate自定义集合类型(下):自动维护双向关系

2009-10-12 00:49 by 老赵, 15324 visits

如果使用NHibernate自带的集合类型,其中一个问题就在于需要在代码中手动维护双向关系,迫使开发人员编写额外的代码。其实这就是集合自定义逻辑的一个应用方面。现在,既然我们已经得到了一个方便的自定义集合的解决方案,那么现在便把“自动维护双向关系”作为目标来实现一番,也算是一个非常典型的示例了。

昨天是休息天,看文章的朋友比较少,如果您遗漏了上一篇的内容,不妨再阅读一次,对理解本文会有一定帮助。

我们已经知道LINQ to SQL是如何自动维护双向关系的,它的做法是在集合被添加或删除元素时发起一个回调函数,而在回调函数内部对某些属性进行设置。我们也可以采用这种方式。不过在此之前,我们必须知道NHibernate在进行集合操作时的一些顺序,例如在加载父实体时,集合属性的set操作和集合元素的添加操作哪个在前,哪个在后。

我们还是使用Question-Answer作为示例:

public class Question
{
    public virtual int QuestionID { get; set; }

    public virtual string Name { get; set; }

    private ISet<Answer> m_answers;
    public virtual ISet<Answer> Answers
    {
        get
        {
            if (this.m_answers == null)
                this.m_answers = new AnswerSet();

            return this.m_answers;
        }
        set
        {
            Console.WriteLine("Set Question.Answers");
            this.m_answers = value;
        }
    }
}

public class AnswerSet : HashedSet<Answer>
{
    public override bool Add(Answer o)
    {
        if (base.Add(o))
        {
            Console.WriteLine("Add Answer");
            return true;
        }

        return false;
    }
}

我们在Question.Answers属性的Set操作与AnswerSet的Add操作中都添加了一些输出文本的代码。然后,我们使用下面的代码进行测试:

[Fact]
public void LazyLoad()
{
    var session = SessionFactory.Instance.OpenSession();

    var question = session.Get<Question>(1);
    question.Answers.Add(new Answer { Name = "Answer", Question = question });
}

[Fact]
public void EagerLoad()
{
    var session = SessionFactory.Instance.OpenSession();
    var question = session
        .CreateCriteria<Question>()
        .Add(Expression.IdEq(1))
        .SetFetchMode("Answers", FetchMode.Eager)
        .UniqueResult<Question>();

    question.Answers.Add(new Answer { Name = "Answer", Question = question });
}

LazyLoad和EagerLoad分别测试的是延迟加载和“饥渴”加载两种情况下的操作顺序。从输出内容里可以发现,两种情况下结果完全相同:

Set Question.Answers
Add Answer
Add Answer
Add Answer

由于原本数据库中该Question有2个Answer对象,因此会输出三条Add Answer信息。可以看出,NHibernate会先设置集合类型,再向其中添加元素。这对我们来说是一个好消息,因为我们可以放心地在Question.Answers的set操作中添加回调函数,然后等待Answer对象一个个添加进来,我们的逻辑为它们一一建立关联。

为了“回调”,我们可以定义一个通用的ObservableSet<T>:

public enum ItemChangedType
{
    Added,
    Removed
}

public class ItemChangedEventArgs<T> : EventArgs
{
    public ItemChangedEventArgs(ItemChangedType type, T item)
    {
        this.Type = type;
        this.Item = item;
    }

    public ItemChangedType Type { get; private set; }

    public T Item { get; private set; }
}

public interface IObservableSet<T> : ISet<T>
{
    event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
}

public class ObservableSet<T> : HashedSet<T>, IObservableSet<T>
{
    public override bool Add(T o)
    {
        if (base.Add(o))
        {
            var e = new ItemChangedEventArgs<T>(ItemChangedType.Added, o);
            this.OnItemChanged(e);
            return true;
        }

        return false;
    }

    public override bool Remove(T o)
    {
        if (base.Remove(o))
        {
            var e = new ItemChangedEventArgs<T>(ItemChangedType.Removed, o);
            this.OnItemChanged(e);
            return true;
        }

        return false;
    }

    public event EventHandler<ItemChangedEventArgs<T>> ItemChanged;

    protected void OnItemChanged(ItemChangedEventArgs<T> e)
    {
        var itemChanged = this.ItemChanged;
        if (itemChanged != null) itemChanged(this, e);
    }
}

显然,修改一个HashedSet元素内容的接口不止Add和Remove两个方法,于是在override的时候就又要缩手缩脚了。最终,我还是通过阅读HashedSet的源代码才意识到所有的接口最终都会调用Add和Remove方法。因此,我们只需要在Add和Remove的时候发起事件即可。于是在Question对象中:

private IObservableSet<Answer> m_answers;
public virtual IObservableSet<Answer> Answers
{
    get
    {
        if (this.m_answers == null)
        {
            this.Answers = new ObservableSet<Answer>();
        }

        return this.m_answers;
    }
    set
    {
        this.ChangeAnswerSet(value);
    }
}

private EventHandler<ItemChangedEventArgs<Answer>> m_answerSetItemChangedHandler;

private void ChangeAnswerSet(IObservableSet<Answer> answers)
{
    if (this.m_answerSetItemChangedHandler == null)
    {
        this.m_answerSetItemChangedHandler = this.OnAnswerSetItemChanged;
    }

    if (this.m_answers != null)
    {
        this.m_answers.ItemChanged -= this.m_answerSetItemChangedHandler;
    }

    this.m_answers = answers;
    this.m_answers.ItemChanged += this.m_answerSetItemChangedHandler;
}

private void OnAnswerSetItemChanged(object sender, ItemChangedEventArgs<Answer> e)
{
    ...
}

目前,在设置Question.Answers属性的时候,我们会为它添加事件处理函数,并且将旧的事件处理函数剥离。至于OnAnswerSetItemChanged中,便是维护Answer和Question的关系了:

private void OnAnswerSetItemChanged(object sender, ItemChangedEventArgs<Answer> e)
{
    var answer = e.Item;

    if (e.Type == ItemChangedType.Added)
    {
        if (answer.Question != null)
        {
            answer.Question.Answers.Remove(answer);
        }

        answer.Question = this;
    }
    else
    {
        answer.Question = null;
    }
}

至此,我们已经在Answer元素添加至Question.Answers集合时保持了逻辑。但是,我们还有两个东西没有搞定:

  • 如果外界有代码直接一锅端地设置Question.Answers集合,那么旧集合中的元素是否要和Question脱离关系?
  • 如果外界有人直接设置Answer.Question属性,是否要将其添加到Question.Answers集合中?

严格说来,这些都是需要的。但是我在这里不选择这种做法,我选择——将Question.Answers集合和Answer.Question属性的set方法都设置为private!在OnAnswerSetItemChanged方法内部,就动用FastReflectionLib来设置answer.Question属性。至于NHibernate,它的操作都是可靠的,我们信任它。您想想看,这么做有什么问题吗?似乎真没有。我们在任何需要设置Answer.Question属性的地方,都可以通过操作Question.Answers集合来实现。

最后的配置自然也是必不可少的:

public class QuestionMap : ClassMap<Question>
{ 
    public QuestionMap()
    {
        Id(q => q.QuestionID).GeneratedBy.Identity();
        Map(q => q.Name);
        HasMany(q => q.Answers)
            .LazyLoad()
            .CollectionType<SetType<Answer, ObservableSet<Answer>, IObservableSet<Answer>>>()
            .KeyColumns.Add("QuestionID")
            .Cascade.All()
            .Inverse();
    }
}

当然,您是否觉得现在自动保持双向关系的做法非常繁琐?

的确如此啊,那么,您是否可以解决(至少缓解)这个问题?

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

35 条回复

  1. 邀月
    *.*.*.*
    链接

    邀月 2009-10-12 09:01:00

    虚心学习!顺便问下楼主:

     get
            {
                if (this.m_answers == null)
                    this.m_answers = new AnswerSet();
    
                return this.m_answers;
            }
    
    

     get
            {
              return (this.m_answers==null)?(new AnswerSet()):this.m_answers;
            }
    

    这两个哪个更标准些,我写的时候老是不想空行,是否妥当?

  2. virus
    *.*.*.*
    链接

    virus 2009-10-12 09:52:00

    @邀月
    这个和标准有关系吗,和空行也没有关系吧
    第二种使用了三目表达式

  3. virus
    *.*.*.*
    链接

    virus 2009-10-12 09:53:00

    都是标准的吧,第一种在理解性方面好一点,容易看明白、
    第二种简洁一点

  4. 老赵
    admin
    链接

    老赵 2009-10-12 09:56:00

    @邀月
    首先,这两种写法含义不同。
    其次,你的写法也最好写成:
    return this.m_answers ?? new AnswerSet();

  5. 邀月
    *.*.*.*
    链接

    邀月 2009-10-12 09:57:00

    楼主:

    virus:
    @邀月
    这个和标准有关系吗,和空行也没有关系吧
    第二种使用了三目表达式


    我是看一些开源的控件,经常把类似的能写成一行的就写成一行。我觉得会看起来更清楚些。但是有些人觉得我这样写很怪。今天看到,所以有此一问。跑题了。

  6. 李永京
    *.*.*.*
    链接

    李永京 2009-10-12 10:14:00

    @邀月
    是写成一行不方便调试,这是商业控件的做法,哈哈

  7. 老赵
    admin
    链接

    老赵 2009-10-12 11:23:00

    @邀月
    关键是清楚,不是为了一行而一行。

  8. 老赵
    admin
    链接

    老赵 2009-10-12 11:23:00

    @李永京
    商业控件既然不开源,为什么要写成这样让自己麻烦?

  9. heros
    *.*.*.*
    链接

    heros 2009-10-12 16:26:00

    还真没这样做过。一般都是先持久化了,再加载出来。加载时nh会自动维护关系。

  10. 老赵
    admin
    链接

    老赵 2009-10-12 16:27:00

    @heros
    这是自然,不过在创建,添加,删除的时候,就要手动操作对象了阿,呵呵。

  11. heros
    *.*.*.*
    链接

    heros 2009-10-12 16:34:00

    的确也是。如果数据量大了,要批量入库前还有实时关系维护的需求,这个就有大用处了。大量数据存盘了再读出来,开销不小。

  12. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-10-12 16:39:00

    @邀月
    这个说“标准”有点牵强了吧,习惯决定了大部分风格。

    要我说,比较通用的做法就是:短的,简单的用条件表达式,长的,复杂的用if/else

  13. 人中共鸣[未注册用户]
    *.*.*.*
    链接

    人中共鸣[未注册用户] 2009-10-13 13:45:00

    老Zhao, 请问NHibernate有自动生成工具吗?你平常用的是什么呢?谢谢.

  14. deerchao
    *.*.*.*
    链接

    deerchao 2009-10-13 23:58:00

    return m_answers ?? (m_answers = new AnswerSet());

  15. 老赵
    admin
    链接

    老赵 2009-10-14 00:05:00

    @deerchao
    这个……好吧……呵呵

  16. 老赵
    admin
    链接

    老赵 2009-10-14 00:05:00

    @人中共鸣
    我不用自动生成啊。
    NH的特点就是丰富的映射方式,很难自动生成的。

  17. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 11:10:00

    你一直抱怨NHibernate的集合映射不方便,实际是因为你还不明白什么是集合映射,“集合映射”并不等同于“关系”,只不过关系数据库的关系在对象模型中用集合来表示而已,说 Linq to Sql 和 Entity Framaework 在这方面强过NHibernate更是无稽之谈,“集合映射”是NHibernate的“专业术语”,那2个根本就没有这种功能(Entity Framaework 可能有这种想法)。

    至于为什么用起来似乎不太方便(尤其是自定义集合),我觉得一方面是因为没有需求,反正我们是从来不用自定义集合的,另一方面恐怕是工具的问题。Linq to Sql 其实就是个代码生成器,Entity Framework 对于普通程序员其实也就是一套开发工具,Hibernate在Java领域有很多好用的工具,但NHiberante(在.net领域)根本一个都没有(ActiveWriter在我看来根本达不到实用的程度)。SubSonic在功能上远不如其他流行的数据访问层框架,但是为什么会有那么多使用者?其实就是工具做的好。

    从集合映射的本质上看,一个“关系”在两个方向上分别维护是合理和正确的,如果你想要只维护一次可以修改(或添加)对象模型实现一个方向上的集合映射(注意,一个数据库表(列)可以同时有多个映射而不冲突),想自动维护另一端请自己实现自动维护的代码(就像微软的“代码生成器”那样)。

  18. 老赵
    admin
    链接

    老赵 2009-10-18 11:47:00

    @Ulysess
    LINQ to SQL有Entity Set,为什么说它没有“集合”这种功能?只不过实现的不灵活而已,但是使用上,自动维护双向关系上,的确比NH要强,或者说“好用”,因为它的Entity Set提供了Hook(修改时发起回调),而NH正是缺少了这个Hook,而我正是在补充这个Hook。
    为什么用起来不太方便,不是工具缺失,而是的确需要写很多东西(不过我现在给出了了通用的实现之后,也不算麻烦了)。你没有自定义集合的需求,那是因为你没有追求这方面的需求,直接用内置的就够了啊,不代表NH就方便了。我承认,直接用内置的,加上代码自动生成,肯定不麻烦。但关键就在于,我不满足“内置”的需求。
    集合映射,的确应该维护双向关系,我的目的就是“简化”这个操作,不需要程序员手动进行。但是你可以想象一下,如果你不使用“自定义集合”,有办法“生成”出“自动维护”的代码吗?除非你有了我现在给出的这部分基础实现,或者你有其他类似的基础实现,否则还是没法“自动生成”的。
    所以,NH麻烦的确就是麻烦,除非补充基础类库,否则你实现不了可用的“代码生成器”的。因为,NH默认实现缺少Hook。

  19. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 13:52:00

    其实我早就想说了,请谦虚一点吧,你以为自己很了不起吗,Hibernate发展这么多年,达到今天这个高度,水平还不如你吗?你从一开始就认定它有地方不符合你的想法->它这里不好用->它全都不好用->它就是个垃圾,这种想法是很危险的,而且你这些都是公开言论,只会被明白人笑话,对自己更没有好处。

    我已经说过了,“集合映射”并不等同于“关系”,只不过关系数据库的关系在对象模型中用集合来表示而已。Hibernate的集合映射中所说的集合,它的模型的成员可以不包含实体类型,也可以包含1个以上的实体类型,表达能力已经超过关系数据库的关系了,而你一直说的“双向关系”就是传统关系数据库的那种关系,仅仅是2个实体间的3种关联,你想出的这个方法也只能通用于这种情况,Linq to Sql 和 Entity Framework 也只能映射这种关系,但 Hibernate 的“集合映射”要强大和复杂多了。

    你说的hook,那其实是Linq to Sql 和 Entity Framework 的内置类型的功能,如果有类库给NHibernate提供这样一个类型并且提供工具自动生成相关代码,结果就和 Linq to Sql 和 Entity Framework 一样,同时可能也就失去了 NHibernate 的集合映射的强大功能,带来的好处仅仅是API简单了一点(只是变简单了一点,并不意味着更好,对于理解了集合映射的人,原来的方式才是正确的)。

  20. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 13:57:00

    Linq to Sql 和 Entity Framework 实际上假定或说限制了能够使用的集合的类型,相当于NHibernate+自定义集合,只不过提供了工具用起来简单,而且它的假定符合大多数人的需求。

  21. 老赵
    admin
    链接

    老赵 2009-10-18 14:26:00

    @Ulysess
    我从文章一开始就说了,我是不太了解NH,也希望NH高手可以指点,它不符合我的想法,要么你可以说我的想法不合适(我文章里也提过这种假设了),或者说NH有我不了解的地方。你觉得我错了,不必说什么NH发展了很多年,我肯定不如它,你就直接说我扩展有哪里错了就行了。

    我说过NH差吗?你看过我写的所有NH文章吗?我一直在夸它,一直在推荐别人用它,夸它比Entity Framework好,夸它比LINQ to SQL好,我只不过指出他某个不方便的地方,你就觉得我说它“全都不好用”,“它就是个垃圾”,究竟谁会被明白人笑话?

    你说NH的集合映射强多了,说了半天也只是重复了我的观点而已。我早就说了NH强大,LINQ to SQL只是“方便”但不够强大。我既然现在能扩展NH,也是依赖了NH的强大和复杂。你说的多种映射方式,我也在项目里用了,而且是结合我现在的做法在用,有问题吗?

    我的Hook是扩展了NH,没有影响NH原有的任何映射能力,只不过它原本使用不方便,于是我扩展了而已。只要原来的方式是正确的,我的方式和原来的方式有什么区别?我让NH的映射能力有任何损失吗?我的确只是扩展了NH的集合映射方式之一,那么我就提高了这一个部分,我没说这是唯一的,最好的,大家只能用这种。我只是说,如果你用了这个,那么可以用我这个方式来“更加方便”——那么我做错了吗?

    其实你也说了,LINQ to SQL是“工具用起来简单,而且它的假定符合大多数人的需求”,那么LINQ to SQL在这个大多数人的需求上是不是比NH方便?我的做法,就是让NH在这方面有不输给LINQ to SQL的“方便”而已,完全是在为NH推广出力。难道坚持认为NH没有问题,对NH就有好处了吗?

  22. 老赵
    admin
    链接

    老赵 2009-10-18 14:27:00

    @Ulysess
    说到底,你真看了我的扩展方式吗?还是只是因为我对你钟爱的NHibernate发表了一点负面言论?
    那么你还是看看我其他几篇NH的文章吧,尤其是这第一篇的前面一大段,把NH说的天上有地上无的,你会看得很爽的:
    http://www.cnblogs.com/JeffreyZhao/archive/2009/09/24/my-view-of-nhibernate-1-lazy-loading.html

  23. 老赵
    admin
    链接

    老赵 2009-10-18 14:36:00

    话说我写了这几篇,都是我对NH的使用感受,有夸它的,有人为它不好的地方。
    例如,我这里说NH对集合的映射方式不够完美,不支持自定义集合:
    http://www.cnblogs.com/JeffreyZhao/archive/2009/10/08/my-view-of-nhibernate-3-collection-support.html

    于是有几个朋友告诉我,NH是可以扩展和使用自定义集合的。于是我看了,了解了,写了这上中下三篇文章,除了给出自定义集合的方式,也让NH的“一部分”功能使用更加方便了。
    某些NH高手水平很厉害,也知道如何讨论,如何给出指点,这就是让别人提高的方式。而不是跑上来就说:“你不懂”,“你以为自己很了不起吗?”,“你以为NH水平还不如你吗?”。

    而且,“非黑即白”是一个再低级不过的逻辑错误了。我认为NH有一些缺点,就说明我认为NH完全就是个垃圾?那么,为什么我又在别的地方说它好呢?

  24. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 15:47:00

    那些我当然看了,我看你的blog也有段时间了,相对于你其它的文章,我明显感到你关于(N)Hibernate这几篇一直对(N)Hibernate不太友好。批评或评论某个事物前请先尝试了解它,在不了解时就不要

    随便否定它,你既然知道自己不是很了解它就更不应该这样,放马后炮不是认真研究技术的人该做的。

    延迟加载那是多么常见的功能,你竟然会认为NHibernate会犯那种低级错误,就算它真的曾经那样实现过,在这么多年的实际应用过程中也早就该发现并修改了,轮不到你我这种水平的人,这足以说明你

    的思想倾向,要是你开始时谦虚点也不会有后来那么丢人的解释了。集合映射是(N)Hibernate非常强大的功能,虽然文档里对此着墨不多,但还是能够看到超越一般“关系”的地方,你连这个都不明白说

    明你就没有认真看过NHibernate的文档,连这么简洁有效的文档都不看,我必须要怀疑你对NHibernate的想法和态度。我不盲信什么权威,我支持怀疑精神,但怀疑和否定是不同的,尤其对于那些久经

    考验的事物更要注意。也可能你对于这种事无所谓,也可能你就是这种性格,那么当我没说。

    我确实很生气,因为NHibernate目前最大的对手就是微软,NHibernate的软肋就是很多人都觉得它复杂不好用,你的文章恰恰在助涨这种说法。你可以去问问JAVA程序员Hibernate怎样,多数应该会认

    为是很方便的(方便到没什么感觉了),原因就是开发工具支持好,如果NHibernate能有那样的工具支持,Entity Framework 也许会晚生好几年(或许不会有了)。

    至于你想要的“自动维护另一端关系”的功能,我认为这根本不是集合映射的问题,这是如何使用集合映射的问题,集合映射的本质就应该是现在这样。由于NHibernate没有像 Linq to Sql 和 Entity

    Framework 那样的简单好用的代码生成器所以才会困难,还有个原因就是JAVA和dotNet内置的集合模型不同以及其他的不同。

    非黑即白似乎是你做的事。

  25. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 15:50:00

    用记事本写的,自动换行造成的格式化错误,对不起了。

  26. 老赵
    admin
    链接

    老赵 2009-10-18 16:18:00

    @Ulysess
    嗯,是,我有些地方是没有看清NHibernate的文档,所以犯错误了,这点我在文章中承认了,以后我会改进的。但是,其实我在写文章的时候还是以为我搞清了。所以我只能保证如果我错了,我会立即承认,但我无法保证我以后还是不会犯错。犯错很正常,我不认为这有什么丢人的。我文章里写了,我提出也是为了让大家指出错误来。

    你生气,你说是因为我助长了NH复杂的说法。那么请你举出实例来,说明NH不复杂,很好用,而不是在这里一味地贬低我,只是因为我说NH复杂。你可以写文章说NH是如何的好用,我写了我的看法,你也可以写你的看法,这样就足够否定我的言论了。

    现在,我只会觉得你是凭着冲动在对人不对事。虽然你的冲动我能理解,毕竟我在说你钟爱的东西的缺点,但还是希望你可以用技术方面的事实来证明你的观点。否则,就算我是有倾向性,但是我说的东西就会因为这个倾向性而改变其在客观上的正确性吗?

    你一直说Entity Framework和LINQ to SQL的优势是有好用的代码生成器,但是我想说,如果没有我这几篇文章,NH光靠代码生成器是无法达到Entity Framework和LINQ to SQL的易用程度的。如果你不认同这一点,那么请给出代码自动生成的方式,可以做到双向的关系维护(这点的确不是集合映射的问题,但是涉及到“是否好用”)。

    我说你非黑即白,是因为你看到我致以NH的某个缺陷,就认为我在说NH完全不好。而最后你说我才是非黑即白,但还是没有给出依据。所以我想是你的气话,故意又针对性的回复言论。那么这点我想我就不接受了。不过还是谢谢你的指点。我会时刻注意避免非黑即白的。

  27. 老赵
    admin
    链接

    老赵 2009-10-18 16:33:00

    @Ulysess
    还有就是,你误会我对NHibernate的感觉了,我根本没有对它不友好,刚好相反的是,它是我目前最喜欢的ORM框架,所以我这几篇文章是希望它变得更好用,所以对它进行扩展。

    你可能还是没有去看我刚才给的链接里面对NHibernate的赞扬,那么我这里转载过来,也可以给其他认为我不喜欢NHibernate的人看看:

    =========================
    这几篇文章里我不打算多谈NHiberante的优点,因为它的优势实在过于明显。如果不考虑Telerik ORM这样的商业框架(因为我没用过,完全不了解),.NET平台上开源和免费的ORM工具几乎没有NHibernate的对手:LINQ to SQL使用的确容易,上手非常快,某些功能也非常细致(稍后会谈到),但对于ORM工具的灵魂“Mapping能力”实在是不敢恭维。前一段时间我也简单了解了一下微软新出的Entity Framework,虽然也秉承了微软一贯的易用性(如强大的LINQ支持),在Mapping能力上也有切实的提高,但是在功能和一些细节控制上还远不如NHibernate。毕竟NHibernate是经历了多年发展,对于各种情况几乎都有应对措施。如延迟与否,是使用select还是join获取数据,是否在集合加载时附加条件。此外,NHibernate的Interceptor能力所带来的扩展性也是让我比较满意的,不过这点有机会再详细谈一下。
    =========================

    我希望你在了解了我的态度之后,就可以冷静一些,不要认为我是NHibernate的敌人,然后可以客观地给出NH的使用方法,而不仅仅是不断的攻击我对NH的质疑。

  28. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 17:00:00

    我是有点激动,抱歉。

    我说过了,NHibernate没有好用的工具,这正是它不如 Linq to Sql 和 Entity Framework 的最大的地方,但Hibernate有,而且许多年前就有了,也是很好用的,当然它的工作方式和生成的代码是针对JAVA的规范和风格的,在dotNet程序员看来可能不好用,但至少说明是可以有的。

    “双向的关系维护”本身就不符合NHibernate的集合映射的内容,一个实体类型中的集合可以:使这个实体类型与另一个实体类型建立关系;不与其它实体类型建立关系,而是包含非实体类型;还可以与一个以上实体类型建立关系,比如元素是包含多个实体的component,比如键和值都是实体的字典。而且有关系的2个实体间的关系可以不对称,比如A对B是一对多,B对A是一对一,比如C包含一个集合包含D,但D中没有包含C的集合或类型为C的成员。数据库字段还可以被映射多次,比如某张表被映射为X和Y两个实体(成员可以重叠也可以不同),X对Z是一对多关系,Y对Z是一对一关系,X和Y还可以建立一对一关系呢。还有实体的继承和多态的情况。这些复杂的功能混合在一起很难做出通用的自动维护关系的方法。Linq to Sql 和 Entity Framework 就只是映射关系数据库而已,所以才能像你想的那样自动维护双向关系,因为它们反映出的就是关系数据库,但(N)Hibernate的集合映射不是。

  29. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 17:08:00

    对于(N)Hibernate,集合和实体很接近,和真正的实体一样集合有自己的ID,不仅可以add、remove、clear,还可以整体迁移,给实体中类型是集合的成员直接赋一个新的集合对象是功能,把某个集合从一个实体移动到另一个实体也是功能,这才是集合映射,Linq to Sql 和 Entity Framework 是没有这种“集合映射”的。

  30. 老赵
    admin
    链接

    老赵 2009-10-18 17:18:00

    @Ulysess
    你说的这些都对,体现的是NH灵活强大的集合映射能力,这也是我选择NH的原因,我目前在做的这个项目,把NH的映射方式用了许许多多。
    我说NH的问题,只是说他在LINQ to SQL和Entity Framework实现的那部分功能,也就是60%情况下所用的功能中,不够方便而已。
    这是客观事实,所以我去解决它,而现在有了我的扩展之后,通过代码生成器已经可以很方便了。我的扩展也没有让NH丧失其他能力。
    双向维护的确不是必要的,但是如果我们使用了某种最常见的映射关系,那么如果有双向维护,那么就是很方便的功能,我的目的就是这样。

    你有没有联系方式?比如Email什么的,有机会可以请教一下。我对NH的确了解的确还不够透彻,但是有的时候也不知道哪里去了解更多,例如“整体迁移”我就不知道该怎么搞。文档资料总觉得不太够。唯一算得上是能“系统参考”的也只有这个了,但还是太简单:
    http://nhforge.org/doc/nh/en/index.html

    还有你说的,Java那边对于Hibernate的代码自动生成工具有哪些,能给一些参考资料吗?

  31. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 20:43:00

    我只是一般水平,对这东西的了解也算不上深,顶多就是稍微有点经验,当时对比了各个ORM后我发现这是我们唯一可能的选择,其他的都缺乏某些特性(不过对比是在Linq出来前进行的)。不过就像你说的,确实有很多“不方便”的地方,但是看看JAVA那边,只能羡慕啊……另外我不用JAVA也很久了,不太了解了,但是对Hibernate的支持到处都是啊。

    Hibernate的集合有自己的唯一标识,也可以有版本标识,可以通过给某个类型是集合的属性赋一个新的集合来从数据源删除原集合(只会执行一条delete),也可以把那个集合整体赋给另一个实体对象的相同类型的属性(只会执行一条update),实际上就好像操作一个实体类一样。

    比如有这样的模型
    Account { List<Ticket> }
    当前端传回一个新的Ticket集合时该怎么更新?一种方法是,和原集合对比后决定哪些要增加哪些要删除。假如原来有10个Ticket,新集合完全不包含原来那10个而是包含2个新的,这样的结果可能就是10条delete和2条insert。另一种方法就是把原集合清空然后加入新的,结果应该是1条delete和2条insert,但对集合调用clear是得不到这结果的。(N)Hibernate的最简单的方法就是给集合直接赋值,赋一个新的空集合,然后add新的Ticket进去(用Load,不需要维护Ticket对Account的关联)。要想把这10个Ticket转给另一个新Account,对于(N)Hibernate可能只需要一条update(直接把原来的集合赋到需要的地方)。Linq to Sql 和 Entity Framework 做不到吧,至少目前不能直接做到,而如果能给集合赋值那么你希望的自动维护双向关系就变得困难了,你写的这些代码也都是必须在保证不会出现这种情况的前提下才能正常工作。当然对于大多数应用禁止对集合赋值可能不是什么问题,我举这个例子只是用来说明“集合映射”是什么。

    还有我确实很不能接受你进行“质疑”的措辞。

  32. 老赵
    admin
    链接

    老赵 2009-10-18 21:02:00

    @Ulysess
    我觉得有一点你可以放心的是,Java可以做到的,.NET这边肯定也能做到。如果一直没有人去做,只是真的不需要而已。例如,关于代码的自动生成,如果真有目标的话,随便搞一个也是非常容易的。比如,你觉得什么东西比较重要呢?

    // 措辞……我自认没有过分的措辞。好吧,非技术的就不讨论了……

  33. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-18 21:11:00

    我也觉得需求不足是很重要的原因,另一个重要原因是JAVA的IDE竞争激烈,.net基本没有。但是我真的很希望有,不过 Entity Framework 出来了,希望越来越小了。

  34. Ulysess[未注册用户]
    *.*.*.*
    链接

    Ulysess[未注册用户] 2009-10-19 10:30:00

    关于这个问题我还想和你再讨论一下,给我发个邮件吧,地址其实给过你好几次了。

  35. 天堂使者
    124.201.115.*
    链接

    天堂使者 2011-04-12 17:21:08

    看过了你俩的讨论,受益了。希望你们能够把你们私下讨论的内容公布一下,让我们这些对nh追求的人能够有更深入的理解。 谢谢你们。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我