José F. Romaniello

Las aventuras y desventuras de un codificador.

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

0 comentarios:

Publicar un comentario en la entrada