I have a new solution based on a custom ISetAccessor rather than the
IObjectFactory.
As the IBatisNet use ISetAccessor to set the value of a property, we can use
a custom ISetAccessor saves the IsDirty state before actually set the
property value and restore it later.

I attach the unit test.

Hope it works for you.


On Fri, May 29, 2009 at 11:45 PM, Sal Bass <salbass...@hotmail.com> wrote:

>
> I am using auto properties. I have toyed with ibatis constructor loading,
> but because I have never used it on a production app with Ibatis I have been
> hesitant.
>
> I may have to resort to using standard properties and go with Yaojin's
> solution, or your constructor solution. I have been trying to avoid both of
> these but there is no other way....that I can think of.
>
>
>
> ________________________________
> > Date: Fri, 29 May 2009 08:35:46 -0700
> > Subject: Re: Dirty Tracking Issue
> > From: mmccur...@gmail.com
> > To: user-cs@ibatis.apache.org
> >
> > Are you in a situation where you can't use constructor loading of your
> objects? If your not using auto-properties (which it seems your not), this
> might solve your problem entirely.
> >
> > On Fri, May 29, 2009 at 8:32 AM, Yaojian> wrote:
> >
> > That is my mistake, the nested objects loaded from the database is not
> touched so they remains its dirty state set by AOP.
> >
> >
> > I think the simplest solution is to map a column to a field instead of a
> property.
> >
> > for example, a C# property:
> >
> >
> > private string m_Name;
> >
> > public String Name
> > {
> > get { return m_Name; }
> > set { m_Name = value; }
> > }
> >
> > we can map the column "Name" to the "m_Name" field rather than the "Name"
> property in SqlMap:
> >
> >
> >
> >
> >
> > So load object from DB will not fire the dirty tracking injiected by AOP.
> >
> >
> >
> >
> > On Fri, May 29, 2009 at 9:19 PM, Sal Bass> wrote:
> >
> >
> >
> >
> > Yaojian,
> >
> >
> >
> > Thanks! I am still confused though. When I make a call to QueryForObject
> and reset the IsLoading flag to false, that only sets it false for the root
> object. All complex property collections that are loaded at the same time
> will not be reset. Am I missing something obvious?
> >
> >
> >
> >
> >
> >
> >
> >
> >
> > ________________________________
> >
> >> Date: Fri, 29 May 2009 03:52:58 +0800
> >
> >> Subject: Re: Dirty Tracking Issue
> >
> >> From: sky...@gmail.com
> >
> >> To: user-cs@ibatis.apache.org
> >
> >>
> >
> >> If the non-root object is loaded from the database, it should be created
> with the new object factory.
> >
> >> otherwise, it is irrelavant with 'dirty'.
> >
> >>
> >
> >> Bellow is my code for using IObjectFactory, I use a custom
> IObjectFactory for attaching each object to a context variable.
> >
> >>
> >
> >>
> >
> >>
> >
> >> DomSqlMapBuilder builder = CreateDomSqlMapBuilder();
> >
> >>
> >
> >> //Use SqmObjectFactory for attaching objects to the current
> IObjectContext
> >
> >> IObjectFactory originalFactory = new ObjectFactory(true);
> >
> >>
> >
> >> SqmObjectFactory contextableFactory = new
> SqmObjectFactory(originalFactory);
> >
> >> builder.ObjectFactory = contextableFactory;
> >
> >>
> >
> >> ISqlMapper sqlMapper = builder.Configure(m_SqlMapDocument);
> >
> >>
> >
> >>
> >
> >>
> --------------------------------------------------------------------------------------------------------------------------------------------------
> >
> >>
> >
> >> /// Represents the factory of MDA persistent object used by IBatis.NET.
> >
> >>
> >
> >> /// attaches an
> >
> >> /// to each objects created with this factory.
> >
> >> public class SqmObjectFactory : IObjectFactory, IEntityContextBindable
> >
> >>
> >
> >> {
> >
> >> /// Creates an instance.
> >
> >> /// The original .
> >
> >> public SqmObjectFactory(IObjectFactory objectFactoryImpl)
> >
> >>
> >
> >> {
> >
> >> if (objectFactoryImpl == null) throw new
> ArgumentNullException("objectFactoryImpl");
> >
> >> m_ObjectFactoryImpl = objectFactoryImpl;
> >
> >> }
> >
> >>
> >
> >> private readonly IObjectFactory m_ObjectFactoryImpl;
> >
> >>
> >
> >>
> >
> >> private IEntityContext m_EntityContext;
> >
> >>
> >
> >> public IEntityContext EntityContext
> >
> >> {
> >
> >> get { return m_EntityContext; }
> >
> >> set { m_EntityContext = value; }
> >
> >> }
> >
> >>
> >
> >>
> >
> >> /// .
> >
> >> public IFactory CreateFactory(Type typeToCreate, Type[] types)
> >
> >> {
> >
> >> IFactory result = m_ObjectFactoryImpl.CreateFactory(typeToCreate,
> types);
> >
> >>
> >
> >> if (typeof(IEntityContextBindable).IsAssignableFrom(typeToCreate))
> >
> >> {
> >
> >> return new SqmFactory(this, result);
> >
> >> }
> >
> >> return result;
> >
> >> }
> >
> >>
> >
> >> private class SqmFactory : IFactory
> >
> >>
> >
> >> {
> >
> >> public SqmFactory(IEntityContextBindable objectContextable, IFactory
> factory)
> >
> >> {
> >
> >> if (objectContextable == null) throw new
> ArgumentNullException("objectContextable");
> >
> >>
> >
> >> if (factory == null) throw new ArgumentNullException("factory");
> >
> >>
> >
> >> m_ObjectContextable = objectContextable;
> >
> >> m_Factory = factory;
> >
> >> }
> >
> >>
> >
> >> private readonly IEntityContextBindable m_ObjectContextable;
> >
> >>
> >
> >>
> >
> >> private readonly IFactory m_Factory;
> >
> >>
> >
> >> public object CreateInstance(object[] parameters)
> >
> >> {
> >
> >> Object result = m_Factory.CreateInstance(parameters);
> >
> >> ((IEntityContextBindable)result).EntityContext =
> m_ObjectContextable.EntityContext;
> >
> >>
> >
> >> return result;
> >
> >> }
> >
> >> }
> >
> >> }
> >
> >>
> >
> >>
> >
> >> On Fri, May 29, 2009 at 3:45 AM, Sal Bass> wrote:
> >
> >>
> >
> >>
> >
> >>
> >
> >> Yes, I explored that. But how will that work for the complex properties?
> Only the root object would know it's in a loading state.
> >
> >>
> >
> >>
> >
> >>
> >
> >>
> >
> >>
> >
> >> ________________________________
> >
> >>
> >
> >>> Date: Fri, 29 May 2009 02:22:33 +0800
> >
> >>
> >
> >>> Subject: Re: Dirty Tracking Issue
> >
> >>
> >
> >>> From: sky...@gmail.com
> >
> >>
> >
> >>> To: user-cs@ibatis.apache.org
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> We can bypass to set 'dirty' if the AOP generation mechanism can know
> an object is in 'loading' state.
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> We can use a custom IBatisNet.Common.Utilities.IObjectFactory to mark
> an object 'loading'.
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> And we can use a wrapped ISqlMapper to clean the 'loading' flag as:
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> public object QueryForObject(string statementName, object
> parameterObject) {
> >
> >>
> >
> >>> Object result = originalSqlMapper.QueryForObject(...);
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> result.IsLoading = false;
> >
> >>
> >
> >>> return result;
> >
> >>
> >
> >>> }
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> Yaojian
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> On Fri, May 29, 2009 at 1:55 AM, Sal Bass> wrote:
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> I am having a dilema with implementing dirty tracking on my entities. I
> am using AOP to mark an entity as "dirty" when a property is set. The
> problem occurs when I load the entities using Ibatis because it sets the
> properties during mapping which makes the entity dirty (no, I can't use
> constructor mapping here). So, I use a RowDelegate to mark the entity clean
> before returning it. Works great....except for when I am loading a root
> object with several complex properties (ILists of other entities). The
> RowDelegate is obviously not fired for each complex property, so they are
> returned as dirty.
> >
> >
> >
> >>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> Any idea of how I can get at all of the complex properties to mark them
> clean before returning the entity?
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> _________________________________________________________________
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> Hotmail® goes with you.
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> http://windowslive.com/Tutorial/Hotmail/Mobile?ocid=TXT_TAGLM_WL_HM_Tutorial_Mobile1_052009
> >
> >
> >
> >>
> >
> >>
> >
> >>>
> >
> >>
> >
> >>>
> >
> >>
> >
> >> _________________________________________________________________
> >
> >>
> >
> >> Windows Live™: Keep your life in sync.
> >
> >>
> >
> >> http://windowslive.com/explore?ocid=TXT_TAGLM_BR_life_in_synch_052009
> >
> >>
> >
> > _________________________________________________________________
> >
> > Insert movie times and more without leaving Hotmail®.
> >
> >
> http://windowslive.com/Tutorial/Hotmail/QuickAdd?ocid=TXT_TAGLM_WL_HM_Tutorial_QuickAdd1_052009
> >
> >
> >
> >
> >
> >
> >
> > --
> > Michael J. McCurrey
> > Read with me at http://www.mccurrey.com
> _________________________________________________________________
> Windows Live™: Keep your life in sync.
> http://windowslive.com/explore?ocid=TXT_TAGLM_BR_life_in_synch_052009
>
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;

using IBatisNet.Common.Utilities.Objects.Members;
using IBatisNet.DataMapper;
using IBatisNet.DataMapper.Configuration;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using NUnit.Framework;

using Assert=NUnit.Framework.Assert;

namespace Halcyon.Fundamental.IBatisNet
{

    public interface IDirtyAware
    {
        bool IsDirty { get; set; }
    }

    //CREATE TABLE dbo.IdName (Id int not null primary key, Name varchar(10) 
not null);
    public class IdName : IDirtyAware
    {
        public int Id { get; set; }

        private bool m_IsDirty;

        private string m_Name;

        public String Name
        {
            get { return m_Name; }
            set
            {
                m_Name = value;
                m_IsDirty = true;   //simulate the AOP behavior
            }
        }

        public bool IsDirty
        {
            get { return m_IsDirty; }
            set { m_IsDirty = value; }
        }
    }

    [TestFixture]
    [TestClass]
    public class IdNameFixture
    {
        public IdNameFixture() { }

        #region Additional test attributes
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        //
        // You can use the following additional attributes as you write your 
tests:
        //
        // Use ClassInitialize to run code before running the first test in the 
class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [Test]
        [TestMethod]
        public void TestDefaultAccessor()
        {
            IdName obj = new IdName() { Id = 1, Name = "Hello" };

            ISqlMapper sqlMapper = TestHelper.CreateSqlMapper();
            using (new TransactionScope())
            {
                sqlMapper.Insert("IdNameMap.InsertObject", obj);

                IdName loaded = 
sqlMapper.QueryForObject<IdName>("IdNameMap.FindByPrimaryKey", 1);
                Assert.IsNotNull(loaded);
                Assert.IsTrue(loaded.IsDirty);  //with the default accessor, 
the AOP set the object state to dirty.
            }
        }

        [Test]
        [TestMethod]
        public void TestDirtyAwareAccessor()
        {
            DomSqlMapBuilder builder = TestHelper.CreateDomSqlBuilder();
            builder.SetAccessorFactory = new DirtyAwareSetAccessorFactory(new 
SetAccessorFactory(true));
            ISqlMapper sqlMapper = TestHelper.CreateSqlMapper(builder);

            IdName obj = new IdName() { Id = 1, Name = "Hello" };

            using (new TransactionScope())
            {
                sqlMapper.Insert("IdNameMap.InsertObject", obj);

                IdName loaded = 
sqlMapper.QueryForObject<IdName>("IdNameMap.FindByPrimaryKey", 1);
                Assert.IsNotNull(loaded);
                Assert.IsFalse(loaded.IsDirty);  //with the new accessor, the 
dirty state set by AOP is overrided.
            }
        }

        public class DirtyAwareSetAccessorFactory : ISetAccessorFactory
        {
            public DirtyAwareSetAccessorFactory(ISetAccessorFactory original)
            {
                m_Original = original;
            }

            private ISetAccessorFactory m_Original;

            #region ISetAccessorFactory Members

            public ISetAccessor CreateSetAccessor(Type targetType, string name)
            {
                ISetAccessor originalAccessor = 
m_Original.CreateSetAccessor(targetType, name);
                ISetAccessor result = new 
DirtyAwareSetAccessor(originalAccessor);
                return result;
            }

            #endregion
        }

        public class DirtyAwareSetAccessor : ISetAccessor
        {
            public DirtyAwareSetAccessor(ISetAccessor original)
            {
                m_Original = original;
            }

            private ISetAccessor m_Original;

            #region IAccessor Members

            public Type MemberType
            {
                get { return m_Original.MemberType; }
            }

            public string Name
            {
                get { return m_Original.Name; }
            }

            #endregion

            #region ISet Members

            public void Set(object target, object value)
            {
                IDirtyAware da = target as IDirtyAware;
                if (da != null)
                {
                    bool dirtyState = da.IsDirty; //saves the dirty state 
before set an AOP injected property
                    m_Original.Set(target, value);
                    da.IsDirty = dirtyState;    //restore the dirty state
                }
                else
                {
                    m_Original.Set(target, value);
                }
            }

            #endregion
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper"; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";>
    <settings>
        <setting useStatementNamespaces="true"/>
        <setting cacheModelsEnabled="${isCacheEnabled}"/>
        <setting useReflectionOptimizer="true"/>
    </settings>

    <providers embedded="${mapDir}.Providers.config"/>

    <!-- Database connection information -->
    <database>
        <provider name="sqlServer2.0"/>
        <dataSource name="Halcyon.Fundamental" connectionString="${connectionString}"/>
    </database>

    <alias>
        <typeAlias alias="IdName" type="Halcyon.Fundamental.IBatisNet.IdName, Halcyon.Fundamental.Tests"/>
    </alias>

    <sqlMaps>
        <sqlMap embedded="Halcyon.Fundamental.IBatisNet.SqlMaps.IdName.ibn.xml"/>
    </sqlMaps>

</sqlMapConfig>

Reply via email to