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 <[email protected]> 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: [email protected]
> > To: [email protected]
> >
> > 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: [email protected]
> >
> >> To: [email protected]
> >
> >>
> >
> >> 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: [email protected]
> >
> >>
> >
> >>> To: [email protected]
> >
> >>
> >
> >>>
> >
> >>
> >
> >>> 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>