当禁用自动脏检查并手动加载对象图时,NHibernate不进行级联保存

时间:2009-11-11 14:30:24

标签: nhibernate

这是一个很长的镜头,任何人都会处理这个问题,但这里有。

首先,我禁用了NHibernate的自动脏检查行为。我之所以这样做是因为我不希望NHibernate保存它在提交事务时知道的每个更改对象(FlushMode = Commit),因为它有时会过度热心并试图保存大量的巨大对象图,因为我改变了对象中的某些东西。我按照此处的说明进行操作:http://fabiomaulo.blogspot.com/2009/03/ensuring-updates-on-flush.html

其次,我有一种情况需要使用自定义SQL加载实体,因此我有一些动态SQL使用ISession.CreateSQLQuery()加载这一个对象图。在我这样做之后,我在我加载的实体上调用ISession.Lock()。我真的不知道这是做什么的,但如果我不这样做,对自定义加载对象的更改将永远不会被保存。

现在的问题是,如果我在使用自定义SQL查询加载的对象内的集合中添加或删除对象,NHibernate不会保存它们。通过自动脏检查(默认),它们保存,因为它只保存整个对象图,因为所有关系都标记为cascade =“save-update”。但是,由于我已经进行了自动脏检查,所以它并没有完成所有关系。

似乎自从我调用ISession.Lock()以来,NHibernate知道我的所有对象,但这些对象中的集合有些不同。

如果有人有任何想法,他们将不胜感激。

编辑:关于我正在做什么的更多细节。我有这个方法在哪里查询。从查询的复杂性可以看出,HQL不是一个选项(我总是喜欢HQL或ISession.CreateCriteria()而不是CreateSQLQuery())。

        public IList<MaterialGroup> GetResults(IList<long> takeOffItemIds)
        {
            if (takeOffItemIds == null || takeOffItemIds.Count == 0)
                return new List<MaterialGroup>();

            var query =
@"with MaterialGroupsByTakeOffItem (MaterialGroupId, TakeOffItemId) as
(
    select MaterialGroupId, ParentTakeOffItemId
    from MaterialGroups mg
    inner join TakeOffItems toi on toi.TakeOffItemId = mg.ParentTakeOffItemId
    where ParentTakeOffItemId is not null
    union all
    select grp.MaterialGroupId, parent.TakeOffItemId
    from MaterialGroups grp
    inner join MaterialGroupsByTakeOffItem parent on parent.MaterialGroupId = grp.ParentMaterialGroupId
)
select {mg.*}, {md.*}, {mi.*}, {mc.*} from MaterialGroupsByTakeOffItem mgbtoi
inner join MaterialGroups {mg} on {mg}.MaterialGroupId = mgbtoi.MaterialGroupId
left outer join MaterialDetails {md} on {md}.MaterialDetailsId = {mg}.MaterialGroupId
left outer join MaterialItems {mi} on {mi}.MaterialItemId = {md}.PartId
left outer join MaterialCodes {mc} on {mc}.MaterialCodeId = {mi}.CodeId
";

            if (takeOffItemIds.Count == 1)
                query += "where mgbtoi.TakeOffItemId = " + takeOffItemIds[0];
            else
                query += "where mgbtoi.TakeOffItemId in (" + takeOffItemIds.ToCommaDelimitedString() + ")";

            return _session.CreateSQLQuery(query)
                .AddEntity("mg", typeof(MaterialGroup))
                .AddJoin("md", "mg.MaterialDetails")
                .AddJoin("mi", "md.Part")
                .AddJoin("mc", "mi.Code")
                .List<MaterialGroup>();
        }

此方法返回属于名为TakeOffItem的另一个实体的MaterialGroup对象列表。我获取MaterialGroup对象列表并将它们放入TakeOffItem.MaterialGroups。

    public override TakeOffItem Get(long id)
    {
        var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

        var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
            materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

        var takeOffItem = base.Get(id);
        _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
        _session.Lock(takeOffItem, LockMode.None);
        return takeOffItem;
    }        

    public void AssignMaterialGroups(long takeOffItemId, IHasMaterialGroups parent, IList<MaterialGroup> allMaterialGroups, 
        Dictionary<long, IList<FabricationNote>> fabricationNotesByMaterialDetailsId)
    {
        if (parent is TakeOffItem)
            parent.MaterialGroups = allMaterialGroups.Where(mg => mg.ParentTakeOffItem != null && mg.ParentTakeOffItem.Id == takeOffItemId).ToList();
        else if (parent is MaterialGroup)
        {
            var group = (MaterialGroup) parent;
            parent.MaterialGroups = allMaterialGroups.Where(mg => mg.ParentMaterialGroup != null && mg.ParentMaterialGroup.Id == parent.Id).ToList();
            if (group.MaterialDetails != null)
            {
                if (fabricationNotesByMaterialDetailsId.ContainsKey(group.MaterialDetails.Id))
                    group.MaterialDetails.FabricationNotes = fabricationNotesByMaterialDetailsId[group.MaterialDetails.Id];
                else
                    group.MaterialDetails.FabricationNotes = new List<FabricationNote>();
            }
        }
        else
            throw new NotSupportedException();

        foreach (var group in parent.MaterialGroups)
        {
            _session.Lock(group, LockMode.None);
            if (group.MaterialDetails != null)
                _session.Lock(group.MaterialDetails, LockMode.None);
            AssignMaterialGroups(takeOffItemId, group, allMaterialGroups, fabricationNotesByMaterialDetailsId);
        }
    }

这有几个问题。首先,因为我在TakeOffItem实体上手动填充集合,所以NHibernate认为TakeOffItem现在很脏。我在TakeOffItem.MaterialGroups上有cascade =“save-update”(并且在那之下有更多的级联关系),并且由于TakeOffItem被认为是脏的,它将在保存时保存整个TakeOffItem对象图。如果我真的想保存TakeOffItem,那就没关系,但是如果我不想保存TakeOffItem,它最终会做很多基本没必要的查询。

为了解决其中一些问题,我提出了Fabio的代码,该代码将禁用NHibernate中的自动脏检查行为。现在它只会保存我称之为SaveOrUpdate()的东西(以及级联关系),所以NHibernate认为所有这些其他对象都是脏的并不重要,因为当我刷新它时它不会保存它们会话。但是现在其他东西被打破了,因为如果我在MaterialGroup对象中更改集合(例如,TakeOffItem.MaterialGroups [0] .MaterialGroups.Add(something)),NHibernate并没有意识到它需要保存这些对象。如果我删除所有自定义加载代码,Fabio的代码工作正常。但出于优化原因,我需要自定义加载代码。

我认为问题的一部分还源于这样一个事实:我无法告诉NHibernate一个实体不脏(如果有办法,我很想知道!)。我真的希望能够进行自定义加载,然后告诉NHibernate,“嘿,这整个对象图并不脏,假装你只是加载它。”

再次感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

看起来你正在使用CreateSql做一些有趣的事情 我猜你不会从中返回实体。 请发布您正在使用CreateSqlQuery进行的任何操作 你绝对应该被迫使用session.Lock

答案 1 :(得分:0)

我弄清楚了问题,而且我所做的事情是错误的并不是很明显。我想这就是你试图破解NHibernate时所得到的。

问题在于此代码:

public override TakeOffItem Get(long id)
{
    var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

    var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
        materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

    var takeOffItem = base.Get(id);
    _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
    _session.Lock(takeOffItem, LockMode.None);
    return takeOffItem;
}        

问题是我在加载TakeOffItem(父实体)之前通过自定义查询进行操作。通常,当您使用NHibernate加载内容时,父项首先被加载,并且会话中的已知实体列表将在子项之前具有父项。如果在子项之前列表中没有父项,则自动脏检查代码不起作用,因为它按顺序遍历已知实体列表,将子实体标记为脏的。好吧,如果它试图在父节点之前处理子节点,则在 NHibernate检查它是否需要保存之后,子实体被标记为脏,因此子节点不会被保存。 / p>

我不知道ISession.Lock()的作用,但是像Ayende在他的回答中所说,我不需要再这样做了。所以我拿出了Lock()调用。

这是新代码的样子:

public override TakeOffItem Get(long id)
{
    // Moved this line up
    var takeOffItem = base.Get(id);

    var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

    var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
        materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

    _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
    _session.Lock(takeOffItem, LockMode.None);
    return takeOffItem;
}        

故事的寓意是:如果禁用自动脏检查行为并且您自定义加载实体,请确保在子项之前加载父项。