I’m working with an MS-SQL database with tables that use a customized date/time format stored as an integer. The format maintains time order, but is not one-to-one with ticks. Simple conversions are possible from the custom format to hours / days / months / etc. – for example, I could derive the month with the SQL statement:
SELECT ((CustomDateInt / 60 / 60 / 24) % 13) AS Month FROM HistoryData
From these tables, I need to generate reports, and I’d like to do this using LINQ-to-SQL. I’d like to have the ability to choose from a variety of grouping methods based on these dates (by month / by year / etc.).
I’d prefer to use the group command in LINQ that targets one of these grouping methods. For performance, I would like the grouping to be performed in the database, rather than pulling all my data into POCO objects first and then custom-grouping them afterwards. For example:
var results = from row in myHistoryDataContext.HistoryData
group row by CustomDate.GetMonth(row.CustomDateInt) into grouping
select new int?[] { grouping.Key , grouping.Count() }
How do I implement my grouping functions (like CustomDate.GetMonth) so that they will be transformed into SQL commands automatically and performed in the database? Do I need to provide them as Func<int, int> objects or Expression<> objects, or by some other means?
You can’t write a method and expect L2S to automatically know how to take your method and translate it to SQL. L2S knows about some of the more common methods provided as part of the .NET framework for primitive types. Anything beyond that and it will not know how to perform the translation.
If you have to keep your db model as is:
You can define methods for interacting with the custom format and use them in queries. However, you’ll have to help L2S with the translation. To do this, you would look for calls to your methods in the expression tree generated for your query and replace them with an implementation L2S can translate. One way to do this is to provide a proxy
IQueryProviderimplementation that inspects the expression tree for a given query and performs the replacement before passing it off to the L2SIQueryProviderfor translation and execution. The expression tree L2S will see can be translated to SQL because it only contains the simple arithmetic operations used in the definitions of your methods.If you have the option to change your db model:
You might be better off using a standard
DateTimecolumn type for your data. Then your could model the column asSystem.DateTimeand use its methods (which L2S understands). You could achieve this by modifying the table itself or providing a view that performs the conversion and having L2S interact with the view.Update:
Since you need to keep your current model, you’ll want to translate your methods for L2S. Our objective is to replace calls to some specific methods in a L2S query with a lambda L2S can translate. All other calls to these methods will of course execute normally. Here’s an example of one way you could do that…
Here we have a class that defines a lambda expression for getting the month from an integer time. To avoid defining the math twice, you could compile the expression and then invoke it from your
GetMonthmethod as shown here. Alternatively, you could take the body of the lambda and copy it into the body of theGetMonthmethod. That would skip the runtime compilation of the expression and likely execute faster — up to you which you prefer.Notice that the signature of the
GetMonthExpressionlambda matches theGetMonthmethod exactly. Next we’ll inspect the query expression usingSystem.Linq.Expressions.ExpressionVisitor, find calls toGetMonth, and replace them with our lambda, having substitutedtwith the value of the first argument toGetMonth.The first class here will visit the query expression tree and find references to
GetMonth(or any other method requiring substitution) and replace the method call. The replacement is provided in part by the second class, which inspects a given lambda expression and replaces references to its parameters.Having transformed the query expression, L2S will never see calls to your methods, and it can now execute the query as expected.
In order to intercept the query before it hits L2S in a convenient way, you can create your own
IQueryableprovider that is used as a proxy in front of L2S. You would perform the above replacements in your implementation ofExecuteand then pass the new query expression to the L2S provider.