José F. Romaniello

Las aventuras y desventuras de un codificador.

If you read my blog backward you will find some implementations of IDataErrorInfo. I’ve even implemented IDataErrorInfo with AOP in unhaddins. I found the interface useful when I want to show validations errors through a DataBinding infrastructure, WPF, WinForms or even Asp.Net MVC.

In my last post, there are some comments about service locator:

  1. Mauricio Scheffer said; “It's just easier and more maintainable to use real dependency injection.”
  2. Ivan Korneliuk said; “I prefer to inject IServiceLocator abstraction for such classes, rather than using global ServiceLocator.”

And I agree with both, but there are corner cases.

So, this is all I need to implement IDataErrorInfo in my base entity:

public class BaseEntity : ComparableEntity, IDataErrorInfo
{
    private static IEntityValidator entityValidator;

    private static IEntityValidator EntityValidator
    {
        get
        {
            if (entityValidator == null)
            {
                return entityValidator 
                        = ServiceLocator
                                .Current
                                .GetInstance<IEntityValidator>();
            }
            return entityValidator;
        }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            var errors = EntityValidator
                    .Validate(this, columnName)
                    .Select(iv => iv.Message)
                    .ToArray();
            return string.Join(Environment.NewLine, errors);
        }
    }

    string IDataErrorInfo.Error
    {
        get
        {
            var errors = EntityValidator
                .Validate(this)
                .Select(iv => iv.Message)
                .ToArray();

                return string.Join(Environment.NewLine, errors);
        }
    }
}

Yes, there are some posts about dependency injection in entities, and I’ve done months ago, but it is over complicated for this case. So, with this code, I am not injecting IServiceLocator, and I am not injecting IValidatorEngine. I’m just using a good pattern :)

You can test this code with the code in my previous post.

| More

This a common question, and the answer usually is that this kind of code is harder to test. Really? For me this is not so true.

This is an example test:

[Test]
public void do_something_save_the_object_properly()
{
    var daoMock = new DaoMock();

    //initializing my servicelocator stub.
    ServiceLocatorStub.Create().AddInstance<IDao>(daoMock);

    //this component use service locator internally
    var component = new AComponent(); 
    component.DoSomething();

    Assert.AreEqual("hey", daoMock.WasCalledWith); 

}

The implementation of DoSomething look as follows:

public void DoSomething()
{
    ServiceLocator.Current.GetInstance<IDao>().Save("hey");
}

The “DaoMock” implementation is not so usefull, but:

public class DaoMock : IDao
{
    public string WasCalledWith { get; private set; }
    public void Save(string anObject)
    {
        WasCalledWith = anObject;
    }
}

And finally here is the ServiceLocatorStub:

public class ServiceLocatorStub : ServiceLocatorImplBase 
{
    private readonly IDictionary<Type, ICollection<object>> 
        registeredTypes = new Dictionary<Type, ICollection<object>>();

    private ServiceLocatorStub()
    {}

    public ServiceLocatorStub AddInstance<TService>(TService instance)
    {
        ICollection<object> instanceCollection;
        if(!registeredTypes.TryGetValue(typeof(TService), out instanceCollection))
        {
            instanceCollection = new List<object>();
            registeredTypes[typeof (TService)] = instanceCollection;
        }
        instanceCollection.Add(instance);
        return this;
    }

    public static ServiceLocatorStub Create()
    {
        var fakeServiceLocator = new ServiceLocatorStub();
        ServiceLocator.SetLocatorProvider(() => fakeServiceLocator);
        return fakeServiceLocator;
    }

    protected override object DoGetInstance(Type serviceType, string key)
    {
        return registeredTypes[serviceType].FirstOrDefault();
    }

    protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
    {
        return registeredTypes[serviceType];
    }
}

The constructor of the class is private, you must call Create(), this will return a new instance and set this instance as the current service locator. Then you can add services (mocks or stubs) fluently!

You don't need to reference a concrete container, neither the implementation of the service locator for the container.

| More

I’ve been using since long time ago the Ghostbuster that Fabio Maulo wrote.

I don’t remember how many problems in my mappings I have detected with this piece of code. Just to enumerate a few type of problems:

  • A null column is mapped to a non nullable property. This produce a superfluous update.
  • A bad user type.
  • An enum mapped as an int.

Today I was searching for a problem and I had my “wajaaaa” moment when I remembered this old post in the nhforge wiki.  So, I changed the ghostbuster to show the name of the dirty properties, also it is showing the previous state and the current state of the property.

This is the new code:

/// <summary>
/// ghostbuster
/// </summary>
[TestFixture]
public class GhostBuster
{
    private const string DefaultIdName = "ID";
    private Configuration cfg;
    private ISessionFactory sessions;

    [Test, Explicit]
    public void UnexpectedUpdateDeleteOnFetch()
    {
        cfg = new Configuration().Configure();
        sessions = cfg.BuildSessionFactory();
        PersistingMappings(null);
    }

    private void PersistingMappings(ICollection<string> entitiesFilter)
    {
        var invalidUpdates = new List<string>();
        var nop = new NoUpdateInterceptor(invalidUpdates);

        IEnumerable<string> entitiesToCheck;
        if (entitiesFilter == null)
        {
            entitiesToCheck = cfg.ClassMappings.Select(x => x.EntityName);
        }
        else
        {
            entitiesToCheck = from persistentClass in cfg.ClassMappings
                              where entitiesFilter.Contains(persistentClass.EntityName)
                              select persistentClass.EntityName;
        }

        foreach (string entityName in entitiesToCheck)
        {
            EntityPersistenceTest(invalidUpdates, entityName, nop);
        }

        if (invalidUpdates.Count > 0)
        {
            //if (logError.IsDebugEnabled)
            //{
            Console.WriteLine("  ");
            Console.WriteLine("------ INVALID UPDATES -------");
            invalidUpdates.ForEach(Console.WriteLine);
            Console.WriteLine("------------------------------");
            //}
        }
        Assert.AreEqual(0, invalidUpdates.Count, "Has unexpected updates.");
    }

    private void EntityPersistenceTest(ICollection<string> invalidUpdates,
                                       string entityName, IInterceptor nop)
    {
        const string queryTemplate = "select e.{0} from {1} e";
        string msg = "s--------" + entityName;
        Console.WriteLine(msg);

        using (ISession s = sessions.OpenSession(nop))
        using (ITransaction tx = s.BeginTransaction())
        {
            IList entityIds = null;
            try
            {
                string queryString = string.Format(queryTemplate, DefaultIdName, entityName);
                entityIds = s.CreateQuery(queryString).SetMaxResults(1).List();
            }
            catch (Exception e)
            {
                Console.WriteLine("Possible METEORITE:" + e.Message);
            }

            if (entityIds != null)
            {
                if (entityIds.Count == 0 || entityIds[0] == null)
                {
                    Console.WriteLine("No instances");
                }
                else
                {
                    if (entityIds.Count > 1)
                    {
                        msg = ">Has " + entityIds.Count + " subclasses";
                        Console.WriteLine(msg);
                    }
                    object entityId = entityIds[0];
                    try
                    {
                        s.Get(entityName, entityId);
                        try
                        {
                            s.Flush();
                        }
                        catch (Exception ex)
                        {
                            string emsg = string.Format("EXCEPTION - Flushing entity [#{0}]: {1}", entityId, ex.Message);
                            Console.WriteLine(emsg);
                            invalidUpdates.Add(emsg);
                        }
                    }
                    catch (Exception ex)
                    {
                        string emsg = string.Format("EXCEPTION - Getting [#{0}]: {1}", entityId, ex.Message);
                        invalidUpdates.Add(emsg);
                        Console.WriteLine(emsg);
                    }
                }
                tx.Rollback();
            }
        }
        msg = "e--------" + entityName;
        Console.WriteLine(msg);
    }

    private class NoUpdateInterceptor : EmptyInterceptor
    {
        private readonly IList<string> invalidUpdates;
        private ISession currentSession;

        public NoUpdateInterceptor(IList<string> invalidUpdates)
        {
            this.invalidUpdates = invalidUpdates;
        }

        public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState,
                                          string[] propertyNames, IType[] types)
        {
            string msg = " FlushDirty :" + entity.GetType().FullName;
            Console.WriteLine(msg);
            Console.WriteLine("Dirty properties: \n-{0}", string.Join("\n-", GetDirtyProperties(entity)));
            invalidUpdates.Add(msg);
            return false;
        }

        public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
        {
            string msg = " Save       :" + entity.GetType().FullName;
            Console.WriteLine(msg);
            invalidUpdates.Add(msg);
            return false;
        }

        public override void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types)
        {
            string msg = " Delete     :" + entity.GetType().FullName;
            Console.WriteLine(msg);
            invalidUpdates.Add(msg);
        }

        public override void SetSession(ISession session)
        {
            this.currentSession = session;
        }

        private string[] GetDirtyProperties( Object entity)
        {
            String className = NHibernateProxyHelper.GuessClass(entity).FullName;
            ISessionImplementor sessionImpl = currentSession.GetSessionImplementation();
            IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
            EntityEntry oldEntry = sessionImpl.PersistenceContext.GetEntry(entity);

            if ((oldEntry == null) && (entity is INHibernateProxy))
            {
                var proxy = entity as INHibernateProxy;
                Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
                oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
            }

            Object[] oldState = oldEntry.LoadedState;
            Object[] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
            Int32[] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);

            return dirtyProps.Select(d => string.Format("{0} => Old state: {1}. Current state: {2}. {3}", 
                persister.PropertyNames[d], 
                oldState[d] ?? "null",
                currentState[d] ?? "null",
                oldState[d] == null 
                   && currentState[d] != null ? "Your property should be nullable or your data is corrupted" : string.Empty)).ToArray();
        }

    }




}

The trick is in the GetDirtyProperties method.

As if this were not enough, it also show a descriptive message when the oldState is null and the currentState isn't null. This is a common problem when a nullable column, containing null values, is mapped to a non nullable property.

This is an example output:

image

| More