1

I am working on a rather larger application that tries to follow a layered architecture pattern. On DBAL I use fluently configured NHibernate. Database objects sometimes have associations like these:

public class HeaderDbo
{
    public HeaderDbo()
    {
        Details = new List<DetailDbo>();
    }
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<DetailDbo> Details { get; set; }
}

public class DetailDbo
{
    public virtual int Id { get; set; }
    public virtual string DetailName { get; set; }
    public virtual HeaderDbo Header { get; set; }
    public virtual RelevantObjectDbo RelevantObject { get; set; }
}

public class RelevantObjectDbo
{
    public virtual int Id { get; set; }
    public virtual string RelevantText { get; set; }
}

Mapping is as follows:

public class HeaderDboMap : ClassMap<HeaderDbo>
{
    public HeaderDboMap()
    {
        Table("Header");
        Id(x => x.Id).Column("Id");
        Map(x => x.Name);
        HasMany(x => x.Details)
            .Inverse()
            .Cascade.All();
    }
}

public class DetailDboMap : ClassMap<DetailDbo>
{
    public DetailDboMap()
    {
        Id(x => x.Id).Column("Id");
        Table("Detail");
        Map(x => x.DetailName);
        References(x => x.Header).Column("HeaderId");
        References(x => x.RelevantObject).Column("RelevantObjectId")
            .Cascade.SaveUpdate();  //??
    }
}

public class RelevantObjectDboMap : ClassMap<RelevantObjectDbo>
{
    public RelevantObjectDboMap()
    {
        Id(x => x.Id).Column("Id");
        Table("RelevantObject");
        Map(x => x.RelevantText);
    }
}

Now, there are application domain entities that the DBOs are mapped to which do not necessarily reflect the database structure one-to-one. For example, Header might stay header, but Detail would, say, form itself from DetailDbo and parts of RelevantObjectDbo. Then the application does its thing over the entities - some transformation of Detail happens, which now needs to be persisted.

Suppose I only affected parts of the Detail entity which need to go into Detail table and don't affect RelevantObject table in any way. It might be a wrong way to think about the model but we also need to be practical about how persisting works. So, say, I would like to only have NHibernate update the Detail table without "touching" anything on the RelevantObject table. This is exactly the question, actually: how do I achieve that?

In reality, of course, the DB model is far bigger and more complicated and so is the application logic. There could be a part of BL that does not deal with the RelevantObject part of the data at all, so even though DBOs are loaded from the db fully, not all the data finds its way into the app model. But to persist the data back to the database - it seems that I need to hydrate the DB model fully and it's not always practical. So, how can we instruct NHibernate to "not touch" RelevantObject - in other words, not update dbo.Detail.RelevantObjectId?

I tried applying different Cascade options to DetailDbo.RelevantObject property, but if it stays at null, NHibernate always wants to set RelevantObjectId to NULL - I suppose, rightfully so.

I don't understand how I can write changes to the data that is relevant to my "part" of the BL without having to load and save half of my database through all the associations.

Thank you!

Yuri Makassiouk
  • 425
  • 3
  • 16
  • I found that if I set the Cascade to None on DetailDbo.RelevantObject and set the property to an "empty" object with the correct ID set on it, then the update happens the way I want it, i.e. the reference dbo.Detail.RelevantObjectId stays set correctly and dbo.RelevantObject is not touched. BUT, this is way too clumsy, I think - isn't there a better way? – Yuri Makassiouk Dec 13 '17 at 11:17
  • One strategy that I am looking at now is: in the repository responsible for both loading entities from db and for saving, we first read the DBO - to the extent that the repository "wants", update that object from the entity and save it back. It's also kind of clumsy but I like the approach because the logic of "what to persist" is contained within the DBAL, which seems fitting, as the entities should be agnostic of persistence details. Trying to implement this now in some economical way... – Yuri Makassiouk Dec 13 '17 at 12:09

1 Answers1

0

How are you performing these updates? If you are not modifying anything on RelevantObject NHibernate will not send an update for that table. For example:

var header = session.Get<HeaderDbo>(1);
header.Details.First().DetailName = "Whatever";
session.Flush();

Should not cause an update to be issued to the RelevantObject table.

Fredy Treboux
  • 3,167
  • 2
  • 26
  • 32
  • That is true. However, the problem is that sometimes we need to persist an entity which isn't necessarily a carbon copy of the DBO. For example, instead of HeaderDbo, DetailDbo, RelevantObjectDbo (which are reflections of database tables) we deal with a BusinessEntity object. And this entity needs to be persisted in dbo.Header and dbo.Detail tables, so it's natural to use HeaderDbo and DetailDbo to perform this "save" but we do not have enough information on BusinessEntity to recreate the whole constellation of DBOs - that's the conflict. – Yuri Makassiouk Dec 14 '17 at 12:19
  • I'm not sure I follow completely. Wouldn't you Get those entities you can get to from the information you have on the BusinessEntity and then update just those? If you update some properties to the values they already have that's fine. I think I need a more complete example of your situation to understand. Anyways, in case that avoiding column-level updates helps you, you can use "DynamicUpdate();" in your mappings for NH to just issue an update only to the modified columns of the entity. – Fredy Treboux Dec 15 '17 at 13:30
  • So the legitimate (and used?) strategy is to load the DBOs from the database and update them from the BusinessEntity - the way I need for my particular business operation? This is kind of what I did in the end, actually and I liked the solution. Was just wondering if I am missing something better. – Yuri Makassiouk Dec 15 '17 at 14:30
  • That sounds good to me, it's usually what I'd do, load the current state from the database and modify as needed. Then you'll need to think what you want to do regarding version/concurrency (e.g. what happens if somebody else updated your BusinessEntity related records in the meantime from somewhere else, are these changes always consistent?). – Fredy Treboux Dec 15 '17 at 15:48