Question

I am creating reusable Data Access Layer for SQL Server for some projects. I have a problem with finding a proper way to pass .NETs decimal as a parameter to query. Most obvious way:

cmd.Parameters.AddWithValue("@P0", parameterValue);

works almost good. The value is stored properly in DB, but SqlClient infers exact type from actual value, so when application passes 12345.678 it declares @P0 as NUMERIC(8,3). When exactly same query is used with other value, that value's precision and scale is declared. For a reason yet not known to me, from SQL Server those queries are not equal and each of them gets separate entry in sys.dm_exec_cached_plans. There is exactly same problem with string - for each length there is a different query plan.

Some of our applications write quite a few records per minute, each with 5-10 decimal values with wildly varying ranges (some sensor data, some currency values). Almost each INSERT gets it's own query plan and after a days there are more than 100k versions of the same query plan cached, taking well over few GBs of RAM.

My idea to deal with is to coalesce all possible types to a few arbitrary chosen types. For decimal, when precision is lower than 20 I set it to 20. When precision is greater than 20, I set it as smallest value divisible by 4 greater than actual precision (e.g. 24, 28, 32, 36). Similar goes for scale (minimum 2, then steps by 2) and strings (minumum 128, then powers of 2). This way seems to keep the problem under control - I've seen around 100 variants of plans for each query instead of 100k and SQL Server uses memory for buffer pool, not for some useless plans.

Is there any way this approach could go wrong? Are there better ways to keep query plans cache under control when using SqlClient and queries loaded with decimals?

List of things I can't do:

  • Use stored procedures - because purpose of this DAL is to create abstract layer for many DBMSes (SQL Server, MS Access, Firebird, Oracle at the moment).
  • Force my fellow teammates to somehow pass actual type with each variable - because that would be cruel.
  • Trash this DAL, use plain old DbProviderFactory and create parameters like it's 1990 - because this DAL also handles query creation for SQL dialects, DB structure creation, etc.
  • Trash this DAL and use "proper" DAO like NHibernate - because it was Not Invented Here.
  • Take this question to dba.stackexchange.com - because it's a problem with SqlClient and the way I use it, not with SQL Server per se.
Was it helpful?

Solution

You correctly identified the key issue here as letting SQL driver decide the sizes, scales, and precisions on your parameters for you. Your idea of reducing the number of query plans by limiting precisions / scales / sizes of parameters also sounds correct. For strings, you can set the size to the largest value that you expect: it should work correctly when your query compares parameter strings with large size limits to varchars of smaller size. Picking just a few precisions for decimals also sounds right. See if you can get it down to just one pair of precision/scale: it may be possible (it worked out very well for our projects at work). If you manage to do it, you'd be able to go to a single query plan per SQL query.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top