Andy Crouch - Code, Technology & Obfuscation ...

Debugging Object Lists

Man Sitting At Deak Looking At Monitor

Photo: Austin Distel - Unsplash

Something that I wish I had written for Visual Studio over the years was an object list debugger. Data Tables in ADO.Net had the DataSet visualizer which allowed you to see the contents. You were able to copy those contents out to Excel if need be and it was genuinely useful.

Something that I coded up some time ago was a class that takes a list of Entity objects and creates a Data Table from it. I developed it to use within a Table Gateway implementation that I created to improve bulk database insert times. As a side note, you really can not beat the speed of the SQlBulkCopy utility class for inserts on large datasets. Perhaps I will write a post on those topics soon.

I was recently tracking down an intermittent bug. The issue was obviously down to some data passed to a processing routine. The idea came to me to use my EntityDataTableFactory class to dump out objects at debug time. I didn’t really have time to create a polished solution. But, my theory worked and by plugging in my factory, I was able to inspect the data in the DataSet visualiser.

See the optional visualiser option when debugging and results below. The visualiser is available when you hover over a Data Table variable.

Visual Studio Debugging Code


Visual Studio Dataset Visualisation

The EntityDataTableFactory class will work on any strongly typed object List<T> type. You need to declare a table to store the resulting Entity table to and then you can use the DataSet visualiser. Using this to inspect the object list takes a couple of lines to set up. But, I find it is easier to use than Watch values and the property viewer.

I have condensed the code into a single namespace below in case you find it useful yourself.

using EntityListViewer.ExtensionMethods;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Reflection;

namespace DataUtilities
{
    public interface IEntityDataTableFactory
    {
        DataTable CreateDataTableFrom<T>(List<T> entityList) where T : class;
    }

    public class EntityDataTableFactory : IEntityDataTableFactory
    {
        public DataTable CreateDataTableFrom<T>(List<T> entityList) where T : class
        {
            if (entityList.IsNullOrEmpty())
                return new DataTable();

            DataTable entityDataTable = CreateDataTableFor(entityList);

            const int MinimumExpectedColumns = 1;
            if (entityDataTable.Columns.Count.IsLessThan( MinimumExpectedColumns))
                return new DataTable();

            foreach (var entity in entityList)
            {
                GenerateDataRowFrom(entity, ref entityDataTable);
            }

            return entityDataTable;
        }

       private DataTable CreateDataTableFor<T>(List<T> entityList) where T : class
        {
            Type classType = entityList.First().GetType();

            List<PropertyInfo> propertyList 
                = classType
                     .GetProperties()
                     .Where(p => p.GetCustomAttributes(typeof(NotMappedAttribute)).Any() == false && 
                                 p.GetCustomAttributes(typeof(DatabaseGeneratedAttribute)).Any() == false)
                     .ToList();

            const int MinimumPropertyCount = 1;
            if (propertyList.Count < MinimumPropertyCount)
                return new DataTable();

            string entityName = classType.UnderlyingSystemType.Name;
            DataTable entityDataTable = new DataTable(entityName);

            foreach (PropertyInfo property in propertyList)
            {
                DataColumn column = new DataColumn();
                column.ColumnName = property.Name;

                Type dataType = property.PropertyType;

                if (IsNullable(dataType))
                {
                    if (dataType.IsGenericType)
                    {
                        dataType = dataType.GenericTypeArguments.FirstOrDefault();
                    }
                }
                else
                {   
                    column.AllowDBNull = false;
                }

                column.DataType = dataType;

                entityDataTable.Columns.Add(column);
            }

            return entityDataTable;
        }


        private void GenerateDataRowFrom<T>(T entity, ref DataTable entityDataTable) where T : class
        {
            Type classType = entity.GetType();

            DataRow row = entityDataTable.NewRow();
            List<PropertyInfo> entityPropertyInfoList = classType.GetProperties().ToList();

            foreach (PropertyInfo propertyInfo in entityPropertyInfoList)
            {
                if (entityDataTable.Columns.Contains(propertyInfo.Name))
                {
                    if (entityDataTable.Columns[propertyInfo.Name].IsNotNull())
                    {
                        row[propertyInfo.Name] = propertyInfo.GetValue(entity, null) ?? DBNull.Value;
                    }
                }
            }

            entityDataTable.Rows.Add(row);
        }

        private bool IsNullable(Type type)
        {
            if (type.IsValueType.IsFalse()) 
                return true; 

            if (Nullable.GetUnderlyingType(type).IsNotNull()) 
                return true; 

            return false; 
        }
    }

    public static class ExtensionMethods
    {
        public static bool IsNull(this object obj)
        {
            return obj == null;
        }

        public static bool IsNotNull(this object obj)
        {
            return obj != null;
        }

        public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
        {
            return enumerable.IsNull() || !enumerable.Any();
        }

        public static bool IsLessThan(this int number, int value)
        {
            return number < value;
        }

        public static bool IsFalse(this bool bln)
        {
            return bln == false;
        }
    }

}

I’d be interested to hear your thoughts on better ways to debug within Visual Studio. Please share them with me via twitter or email.