Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq To Sql surprisingly fast retreving data. Is it normal that it is 10x faster than ADO?

I'm currently learning Linq to Sql and Im very surprised by the performance of selecting data. I'm retreving joined data from few tables. I select about 40k of rows. Mapping this data to objects using ADO times about 35s, using NHbiernate times about 130s and what is suspicious using Linq To Sql only 3,5s. Additionally I would like to write that I'm using immediately loading which looks like:

THESIS th = new THESIS(connectionString);
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<NumericFormula>(x => x.RPN);
dlo.LoadWith<RPN>(x => x.RPNDetails);
dlo.LoadWith<RPNDetail>(x => x.Parameter);
th.LoadOptions = dlo;
th.Log = Console.Out;

Looking to the logs when I'm iterating I can't see that Linq To Sql generate some additional queries to database.

I'm very surprised by huge differences in performance and I wonder that maybe I don't understand something.

Could someone explain me why it works so fast? To measure time I'm using Stopwatch class.

ADO.NET Code:

public static List<NumericFormulaDO> SelectAllNumericFormulas()
{
    var nFormulas = new List<NumericFormulaDO>();

    string queryString = @"
        SELECT *
        FROM    NumericFormula nf 
                Left Join Unit u on u.Unit_Id = nf.Unit_Id 
                Left Join UnitType ut on ut.UnitType_Id = u.UnitType_Id 
                Join RPN r on r.RPN_Id = nf.RPN_Id 
                Join RPNDetails rd on rd.RPN_Id = r.RPN_Id 
                Join Parameter par on par.Parameter_Id = rd.Parameter_Id where nf.NumericFormula_Id<=10000";

    using (var connection = new SqlConnection(connectionString))
    {
        var command = new SqlCommand(queryString, connection);
        connection.Open();
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                var det = new RPNDetailsDO();
                det.RPNDetails_Id = Int32.Parse(reader["RPNDetails_Id"].ToString());
                det.RPN_Id = Int32.Parse(reader["RPN_Id"].ToString());
                det.Identifier = reader["Identifier"].ToString();
                det.Parameter.Architecture = reader["Architecture"].ToString();
                det.Parameter.Code = reader["Code"].ToString();
                det.Parameter.Description = reader["Description"].ToString();
                det.Parameter.Parameter_Id = Int32.Parse(reader["Parameter_Id"].ToString());
                det.Parameter.ParameterType = reader["ParameterType"].ToString();
                det.Parameter.QualityDeviationLevel = reader["QualityDeviationLevel"].ToString();

                if (nFormulas.Count > 0)
                {
                     if (nFormulas.Any(x => x.RPN.RPN_Id == Int32.Parse(reader["RPN_Id"].ToString())))
                     {
                         nFormulas.First(x=>x.RPN.RPN_Id == Int32.Parse(reader["RPN_Id"].ToString())).RPN.RPNDetails.Add(det);
                     }
                     else
                     {
                         NumericFormulaDO nFormula = CreatingNumericFormulaDO(reader, det);
                         nFormulas.Add(nFormula);
                         //System.Diagnostics.Trace.WriteLine(nFormulas.Count.ToString());
                     }
                }
                else
                {
                     NumericFormulaDO nFormula = CreatingNumericFormulaDO(reader, det);
                     nFormulas.Add(nFormula);
                     //System.Diagnostics.Trace.WriteLine(nFormulas.Count.ToString());
                }
            }
        }
    }

    return nFormulas;
}

private static NumericFormulaDO CreatingNumericFormulaDO(SqlDataReader reader, RPNDetailsDO det)
{
    var nFormula = new NumericFormulaDO();
    nFormula.CalculateDuringLoad = Boolean.Parse(reader["CalculateDuringLoad"].ToString());
    nFormula.NumericFormula_Id = Int32.Parse(reader["NumericFormula_Id"].ToString());
    nFormula.RPN.RPN_Id = Int32.Parse(reader["RPN_Id"].ToString());
    nFormula.RPN.Formula = reader["Formula"].ToString();
    nFormula.Unit.Name = reader["Name"].ToString();

    if (reader["Unit_Id"] != DBNull.Value)
    {
        nFormula.Unit.Unit_Id = Int32.Parse(reader["Unit_Id"].ToString());
        nFormula.Unit.UnitType.Type = reader["Type"].ToString();
        nFormula.Unit.UnitType.UnitType_Id = Int32.Parse(reader["UnitType_Id"].ToString());
    }
    nFormula.RPN.RPNDetails.Add(det);
    return nFormula;
}

LINQ to SQL Code:

THESIS th = new THESIS(connectionString);
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<NumericFormula>(x => x.RPN);
dlo.LoadWith<RPN>(x => x.RPNDetails);
dlo.LoadWith<RPNDetail>(x => x.Parameter);
th.LoadOptions = dlo;
th.Log = Console.Out;
var nFormulas = 
    th.NumericFormulas.ToList<NumericFormula>();

NHibernate Code:

IQueryable<NumericFormulaDO> nFormulas =
    session.Query<NumericFormulaDO>()
        .Where(x=>x.NumericFormula_Id <=10000);

List<NumericFormulaDO> nForList =
    new List<NumericFormulaDO>();

nForList = nFormulas.ToList<NumericFormulaDO>();

Related to your comments you can see that in ADO I'm using SqlReader and in LINQ I try to use immediate execution.

Of course it is possible that my mapping "algorithm" in ADO part it's not very good but NHibernate is much more slow than ADO (4x slower) so I wonder if for sure is everything alright in LINQ to SQL part because I think in NHibernate is everything good and after all is much more slow than little confusing ADO part.

Thank you guys for responses.

like image 777
Piotr Czarnecki Avatar asked Feb 18 '12 13:02

Piotr Czarnecki


1 Answers

LINQ-to-SQL consumes ADO.NET and has additional overheads, so no: it shouldn't be faster unless it isn't doing the same work. There was mention of access via ordinals vs names, but frankly that affects micro-seconds, not seconds. It won't explain an order of magnitude change.

The only way to answer this is to trace what LINQ-to-SQL is doing. Fortunately this is simple - you can just do:

dbContext.Log = Console.Out;

which will write the TSQL is executes to the console. There are two options then:

  1. you discover the TSQL isn't doing the same thing (maybe it isn't eager-loading)
  2. you discover the TSQL is valid (=doing the same), but has a better plan - in which case... "borrow" it :p

Once you have the TSQL to compare, test that side-by-side, so you are testing the same work. If you want the convenience without the overheads, I'd look at "dapper" - takes away the boring grunt-work of mapping readers to objects, but very optimised.

like image 73
Marc Gravell Avatar answered Oct 13 '22 09:10

Marc Gravell