views:

819

answers:

3

Dear All:

Hi! These last two weeks have been my first experience with Castle ActiveRecord, and with the ActiveRecord pattern in general. I'm working on a large system that uses it frequently, and I have been finding some strange SQL transaction issues (such as the one below) as I work on it. I'll give a simplified version of the one that has me totally stumped:

THE BACKGROUND:

I have an ActiveRecord class, let's call it User.

Let's say that this user has many "Pet" objects.

[ActiveRecord]
public class User: PersistentBase<User>
{
//...
        [PrimaryKey]
    public long Id
    {
        get;
        set;
    }

    /// <summary>
    /// Date and time the object was first persisted to the database
    /// </summary>
    [Property, ValidateNonEmpty]
    public DateTime CreationDate
    {
        get;
        set;
    }

    /// <summary>
    /// Date and time the object was last persisted to the database
    /// </summary>
    [Property, ValidateNonEmpty]
    public DateTime ModificationDate
    {
        get;
        set;
    }
    /// <summary>
    /// Property used for optimistic concurrency
    /// </summary>
    [Version]
    public int LockCount { get; set; }

[HasMany(typeof(Pet), Cascade = ManyRelationCascadeEnum.SaveUpdate, Lazy = false, OrderBy = "Id")]
        public IList<Pet> Pets { get; private set; }

//...

    protected override bool BeforeSave(IDictionary state)
    {
        bool retval = base.BeforeSave(state);
        DateTime now = DateTime.Now;
        state["CreationDate"] = now;
        state["ModificationDate"] = now;
        return retval;
    }

    /// <summary>
    /// Called when a dirty object is going to be updated in the db.  Use this
    /// hook to update ModificationDate.
    /// </summary>
    /// <param name="id"></param>
    /// <param name="previousState"></param>
    /// <param name="currentState"></param>
    /// <param name="types"></param>
    /// <returns></returns>
    protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types)
    {
        bool retval = base.OnFlushDirty(id, previousState, currentState, types);
        currentState["ModificationDate"] = DateTime.Now;
        return retval;
    }

}

[ActiveRecord]
public class Pet : PersistentBase<Pet>
{

    [PrimaryKey]
    public long Id
    {
        get;
        set;
    }

    /// <summary>
    /// Date and time the object was first persisted to the database
    /// </summary>
    [Property, ValidateNonEmpty]
    public DateTime CreationDate
    {
        get;
        set;
    }

    /// <summary>
    /// Date and time the object was last persisted to the database
    /// </summary>
    [Property, ValidateNonEmpty]
    public DateTime ModificationDate
    {
        get;
        set;
    }
    /// <summary>
    /// Property used for optimistic concurrency
    /// </summary>
    [Version]
    public int LockCount { get; set; }    

//...

[BelongsTo("OwnerId")]
public User User { get; set; }

//...

    protected override bool BeforeSave(IDictionary state)
    {
        bool retval = base.BeforeSave(state);
        DateTime now = DateTime.Now;
        state["CreationDate"] = now;
        state["ModificationDate"] = now;
        return retval;
    }

    /// <summary>
    /// Called when a dirty object is going to be updated in the db.  Use this
    /// hook to update ModificationDate.
    /// </summary>
    /// <param name="id"></param>
    /// <param name="previousState"></param>
    /// <param name="currentState"></param>
    /// <param name="types"></param>
    /// <returns></returns>
    protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types)
    {
        bool retval = base.OnFlushDirty(id, previousState, currentState, types);
        currentState["ModificationDate"] = DateTime.Now;
        return retval;
    }

}

Now, both of them have automatic Id fields (taken care of by SQL Server 2005).

THE PROBLEM:

If I go ahead and add a new pet to a User who already has existing pets and save the User, I see if I run the SQL Profiler that every single one of the pets has had UPDATE called on them... but not a single one was changed at all.

I threw breakpoints everywhere, and found that, when I save the User, each of the pets has "OnFlushDirty" called (again, though they never changed).

An external process that's looking at (and occasionally modifying) these Users and Pets ends up causing severe transaction problems, that could be avoided entirely if the scenario above would ONLY insert the Pet that was added (and not UPDATE the pets that weren't changed).

THE QUESTION:

Am I doing something above that is a big no-no in terms of making sure such a situation doesn't happen?

Thank you for any help you can provide!

* EDIT 1: OnFlushDirty has null previousState._values *

EDIT: OH! I almost forgot the strangest part of all!

When OnFlushDirty gets called on these Pets, the previousState and currentState exists... they both (being a Dictionary) have an internal variable _values which should have the values of the previous and current states...

... only currentStates has this variable populated. previousState's "_values" variable is set to "null". Note that this is on all of the pets that existed before. previousState should always be populated with something, right?

* EDIT 2: After replacing Auto Properties... *

I replaced the Auto Property lists with a traditional private member with property accessors. It didn't seem to make a difference. I put NHProfiler on the system, and found that NHProfiler couldn't connect to my web app if I was running it through IIS (I'm using IIS7/Win7 with Visual Studio 2008).

I figured I'd try changing to using Visual Studio's "ASP.NET Development Server" instead to see if NHProfiler would see the app then.

Two things happened when I did this:

1) NHProfiler saw my app and began collecting data 2) The multiple UPDATES being done on the children went away

However, switching back to IIS7/Win7, the multiple updates continue to happen.

Does this mean that it's potentially some kind of configuration problem? As far as I know, nothing in my configuration should change other than the URL I'm navigating to (http://localhost in IIS, http://localhost:(some_random_port) with the ASP.NET Development Server) when using the different server types. So why do the two situations above suddenly change?

+1  A: 

IIRC castle activerecord relies on NHib.

In such a case, a list has special semantics within Nhib, as it intends it to be an ordered list. So even though you may not have a position property defined, it is updating the elements in the ordered list as though it had that column.

I haven't verified this, but IIRC, this is your issue.

Chad Ruppert
Oh, interesting! I'll try replacing that with a more generic mechanism... would ICollection stop the problem? Or perhaps simply IEnumerable?
EdgarVerona
Check out this link: https://www.hibernate.org/hib_docs/nhibernate/html_single/#collections-persistent and note the paragraph starting with "All collection types except ISet and bag have an index column" There is more info to be had, but I believe this should get you started.
Chad Ruppert
Ah, thank you! I deeply appreciate it! I'm reading through the docs now, I'll give it a shot and let you know how it goes!
EdgarVerona
Ah... bah. I went through and changed everything to try and get it to work... it's using ISets now, but it's still doing the update of all the Pets. =(
EdgarVerona
What are your cascade settings?
Chad Ruppert
Oh, it's "ManyRelationCascadeEnum.SaveUpdate". Is that the cause? I thought even with that setting, it would only update those which have changed?
EdgarVerona
Well, unless you are modifying the collection members from the root object, then saving the root object and wanting it to walk the rest and save them, you shouldn't have that on.
Chad Ruppert
Aye, there's a couple parts of the system where we want to do that... which is sort of the frustrating part. Those parts have necessitated the existence of the property, and I've basically been told that I shouldn't mess with it... and that I should just figure out why when the Save gets called on the parent it's updating the children that weren't changed. =( =(
EdgarVerona
Cascade is your reason. You won't have a lot of ways around it.If you could work it so that the only time you really need that cascade is during save, not updates, you may want to go that route. Loading it lazily might also help, although I have not actually tested that.
Chad Ruppert
Hmm, I'll try the autoproperties suggestion below, and if that doesn't work I'll see if I can get permission to alter how we're doing the saves where they're relying on cascading right now. Thanks!
EdgarVerona
I think that removing cascading updates on the children and doing it manually where needed is going to likely work around the problem, but I need to know what the real problem itself is: why, for example,the previousState._values is set to 0 when OnFlushDirty is called. If I ignore the Cascading updates features, I'll be curing the symptom but not the root problem I think.
EdgarVerona
It is actually by design for cascade, I believe. http://knol.google.com/k/fabio-maulo/nhibernate-chapter-9/1nr4enxv3dpeq/12# also leads me to believe this is the case. Further research indicates that it updates unattached instances. Are you, by chance, lazy loading the collection? You could try loading the collection when you get the instance of the parent (lazy="false") and see if the behavior goes away.
Chad Ruppert
Aye, I'm not lazy loading sadly. =(
EdgarVerona
+1  A: 

It's dead simple. ActiveRecord cannot cope with C#-autoproperties in collections (because of the interface type). Use a backing field instead and initialize it with

private IList<Pet> pets = new List<Pet>();

-Markus

MZywitza
Oh, I didn't know that! Is that in the documentation somewhere? I hadn't seen any mention of that, but it's definitely good to know.I'll give it a shot as soon as I get back to it on Monday and let you know!Do you know of any good resources where I can learn more about these "gotchas" in ActiveRecord?
EdgarVerona
There's got to be some good resources somewhere on this... I've just yet to find it. Either I'm not looking in the right place in the Castle docs, or they're lacking this info... and I can't find a good external reference that'd clue me in to things like this. =(
EdgarVerona
Aye, that wasn't it. I replaced all of the collection autoproperties in the program with explicit properties, and it made no difference... it's still raising the OnFlushDirty event for the unchanged children, with previousState._value still null.
EdgarVerona
Here, I'll update the OP, I've found some strange situations that may effect what's going on.
EdgarVerona
+1  A: 

It happens that in hibernate you must use merge() to make that hibernate "loads" the previuos data of the detached object, in Castle the equivalent is the SaveCopy() method .

nmiranda