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>