Saturday 18 July 2015

Automapper and performance - 2

AutoMapper can cause headache :). 

Recently one of my team members reported that one of his database layer functions started to fail with error - "Invalid attempt to read when no data is present".

IDataReader -> SPOutput
System.Data.IDataReader -> NewDieProject2.SPOutput

Destination path:
SPOutput

Source value:
System.Data.SqlClient.SqlDataReader ---> System.InvalidOperationException: Invalid attempt to read when no data is present.
   at System.Data.SqlClient.SqlDataReader.CheckHeaderIsReady(Int32 columnIndex, Boolean permitAsync, String methodName)
   at System.Data.SqlClient.SqlDataReader.IsDBNull(Int32 i)



There was nothing wrong with the code. It was working fine on local environment. The reason for it to fail was a newbie team member added a function that overrode (cleared) the mapping created inside AutoMapper for mapping an IDataReader to the targeted type. Hence the exception. The error message does not say anything of that sort though. Instead it is a little misleading and cause you to lose many hours.

The other thing that I figured was that using AutoMapper for mapping IDataReader or DataSet to a .NET type is not a great idea. Especially in the cases where output of one SP is mapped to multiple .NET Types - It is difficult to manage the mapping and it is sloooow. Trust me. 

If you want to have less performance issues and errors like the one above, AVOID the usage of automapper for mapping at database layer. 

AutoMapper is great for mapping .NET objects at other layers though :).

Below is one reference code that you can try. You will be surprised that it can behave differently in release mode and debug mode. 

Mapper.CreateMap();
            string constring = "Server=.;Initial Catalog=Temp;Integrated Security=true";
            DateTime startTime = DateTime.Now;
            IDataReader reader = null;
            IList output = null;
            try
            {
                using (SqlConnection conn = new SqlConnection(constring))
                {
                    SqlCommand command = new SqlCommand();
                    command.CommandType = System.Data.CommandType.StoredProcedure;
                    command.CommandText = "usp_GetMultipleRecords";
                    command.Connection = conn;

                    conn.Open();
                    try
                    {
                        reader = command.ExecuteReader();
                        output = Mapper.Map>(reader);
                    }
                    finally
                    {
                        if (reader != null)
                            reader.Dispose();
                    }
                }

                DateTime endTime = DateTime.Now;
                Console.WriteLine(endTime.Subtract(startTime).Milliseconds);
                Console.WriteLine(output.Count);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            Console.Read();