使用自定义日期/时间格式为数据库实施 LINQ to SQL 表达式
我正在使用 MS-SQL 数据库,其中的表使用存储为整数的自定义日期/时间格式。该格式保持时间顺序,但与刻度不是一对一的。可以从自定义格式到小时/天/月/等进行简单的转换。例如,我可以使用 SQL 语句导出月份:
SELECT ((CustomDateInt / 60 / 60 / 24) % 13) AS Month FROM HistoryData
从这些表中,我需要生成报告,并且我想使用LINQ 到 SQL。我希望能够根据这些日期(按月/按年/等)从各种分组方法中进行选择。
我更愿意在 LINQ 中使用针对这些分组方法之一的 group 命令。为了提高性能,我希望在数据库中执行分组,而不是先将所有数据拉入 POCO 对象,然后再对它们进行自定义分组。例如:
var results = from row in myHistoryDataContext.HistoryData
group row by CustomDate.GetMonth(row.CustomDateInt) into grouping
select new int?[] { grouping.Key , grouping.Count() }
如何实现分组函数(如 CustomDate.GetMonth),以便它们自动转换为 SQL 命令并在数据库中执行?我是否需要将它们作为 Func
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您不能编写一个方法并期望 L2S 自动知道如何采用您的方法并将其转换为 SQL。 L2S 了解作为 .NET 框架的一部分为原始类型提供的一些更常见的方法。除此之外,它将不知道如何执行翻译。
如果您必须保持数据库模型不变:
您可以定义与自定义格式交互的方法并在查询中使用它们。但是,您必须帮助 L2S 进行翻译。为此,您需要在为查询生成的表达式树中查找对方法的调用,并将它们替换为 L2S 可以翻译的实现。实现此目的的一种方法是提供代理
IQueryProvider
实现,该实现检查给定查询的表达式树并执行替换,然后将其传递给 L2SIQueryProvider
进行翻译和执行。 L2S 将看到的表达式树可以转换为 SQL,因为它只包含方法定义中使用的简单算术运算。如果您可以选择更改数据库模型:
您最好为数据使用标准
DateTime
列类型。然后,您可以将该列建模为 System.DateTime 并使用其方法(L2S 可以理解)。您可以通过修改表本身或提供执行转换的视图并让 L2S 与视图交互来实现此目的。更新:
由于您需要保留当前的模型,因此您需要将您的方法转换为 L2S。我们的目标是用 lambda L2S 可以转换来替换对 L2S 查询中某些特定方法的调用。对这些方法的所有其他调用当然都会正常执行。下面是您可以执行此操作的一种方法的示例...
这里我们有一个类,它定义了一个 lambda 表达式,用于从整数时间获取月份。为了避免定义两次数学,您可以编译表达式,然后从
GetMonth
方法调用它,如下所示。或者,您可以获取 lambda 的主体并将其复制到GetMonth
方法的主体中。这将跳过表达式的运行时编译,并且可能执行得更快——取决于您的喜好。请注意,
GetMonthExpression
lambda 的签名与GetMonth
方法完全匹配。接下来,我们将使用 System.Linq.Expressions.ExpressionVisitor 检查查询表达式,查找对 GetMonth 的调用,并将它们替换为我们的 lambda,替换为t< /code> 与
GetMonth
第一个参数的值。这里的第一个类将访问查询表达式树并查找对 GetMonth(或任何其他需要替换的方法)的引用并替换方法调用。替换部分由第二个类提供,它检查给定的 lambda 表达式并替换对其参数的引用。
转换查询表达式后,L2S 将永远不会看到对您的方法的调用,并且它现在可以按预期执行查询。
为了以方便的方式在查询到达 L2S 之前拦截查询,您可以 创建您自己的
IQueryable
提供程序,用作 L2S 前面的代理。您将在Execute
的实现中执行上述替换,然后将新的查询表达式传递给 L2S 提供程序。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
IQueryProvider
implementation that inspects the expression tree for a given query and performs the replacement before passing it off to the L2SIQueryProvider
for 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
DateTime
column type for your data. Then your could model the column asSystem.DateTime
and 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
GetMonth
method as shown here. Alternatively, you could take the body of the lambda and copy it into the body of theGetMonth
method. 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
GetMonthExpression
lambda matches theGetMonth
method exactly. Next we'll inspect the query expression usingSystem.Linq.Expressions.ExpressionVisitor
, find calls toGetMonth
, and replace them with our lambda, having substitutedt
with 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
IQueryable
provider that is used as a proxy in front of L2S. You would perform the above replacements in your implementation ofExecute
and then pass the new query expression to the L2S provider.我认为您可以在 DataContext 中注册自定义函数并在 linq 查询中使用它。在这篇文章中得到了很好的解释:http://msdn.microsoft.com/en -us/library/bb399416.aspx
希望有帮助。
I think you can register your custom function in the DataContext and use it in the linq query. In this post is very well explained: http://msdn.microsoft.com/en-us/library/bb399416.aspx
Hope it helps.
找到了对一些现有代码的引用,这些代码实现了 Michael 建议的 IQueryable 提供程序。
http://tomasp.net/blog/linq-expand.aspx
我认为假设代码有效,另一个挥之不去的问题是您必须为包含日期的每种类型拥有一个 Expression 属性。
避免这样做的结果代码似乎有点麻烦(尽管它可以避免您试图通过将计算放入方法中来避免的那种错误):
Group Expression:
Method to Return Group Expression:
I'm not完全确定嵌套的
.Invoke
是否会导致Expandable
表达式出现问题,或者是否需要对概念进行更多调整,但该代码似乎提供了替代方案为简单的数学表达式构建自定义 IQueryProvider。Found a reference to some existing code which implements an IQueryable provider as Michael suggests.
http://tomasp.net/blog/linq-expand.aspx
I think assuming that code works, the other lingering issue is that you would have to have an Expression property for each type which contained the date.
The resulting code for avoiding doing that appears to be a bit cumbersome (though it would avoid the sort of errors you're trying to avoid by putting the calculation in a method):
Group Expression:
Method to Return Group Expression:
I'm not entirely sure whether that nested
.Invoke
would cause problems with theExpandable
expression or whether the concept would have to be tweaked a bit more, but that code seems to supply an alternative to building a custom IQueryProvider for simple mathematical expressions.似乎没有任何方法可以指示 LINQ-to-SQL 调用 SQL UDF。但是,我相信您可以在 System.Linq.Expressions.Expression 树中封装可重用的 C# 实现...
There doesn't appear to be any way to instruct LINQ-to-SQL to call your SQL UDF. However, I believe you can encapsulate a reusable C# implementation in System.Linq.Expressions.Expression trees...