3

I keep coming across the same type of difficulty in several places in my current project.

The simplest instance is as follows:

I have two related entities, Request and RequestAction. Each RequestAction has a status and a timestamp and points back to a Request.

I need to query a collection of Requests based on the status of the most recent related RequestAction.

Example: Get all Requests where the status of the most recent RequestAction is "Open".

Denormalizing the database to make the most recent status a property of the Request entity is not an option.

I need need the same type of filtering in many other places in my application; I keep track of many versions of items for history and auditing, but I usually only want to look at the most recent version of each item.

I can think of two solutions, but neither is particularly appealing.

1) I can write the SQL manually. The raw SQL answer to this problem involves joining the table to itself with an outer join on the timestamp of one instance of the table being larger than the other, then looking for the records where the second table is null, indicating that no larger timestamp could be found. This is how I handeled the scenario in the past. It is efficient in the database as it uses indexes, unlike a subquery with a max in it. However, my development team is being strongly pushed to use entity going forward to talk to the database, so this solution is being strongly discouraged.

2) I can use entity to load all the values into memory and loop through manually in my code to find the values I am looking for. The code would look something like this:

var openRequests = new List<Request>();
var allRequests = context.Requests;
foreach (var request in allRequests)
{
    var recentAction = request.RequestActions
        .OrderByDescending(c => c.ActionTimestamp)
        .First();
    if (recentAction.Status == "Open")
    {
        openRequests.Add(request);
    }
}

While this would provide me with the results I want, it is horribly inefficint and a huge waste of resources and execution time. The database I am querying against is very large and iterating through each record is really not feasable.

Is there an efficient way to do this using entity? I find it hard to imagine that I am the only one who has ever needed this type of functionality.

4

2 回答 2

2

SQL 端识别任何打开请求操作的最新时间戳不会是:

;WITH MostRecentActions AS 
(
  SELECT RequestID, ActionTimestamp,
    /* other relevant RequestAction columns, */
    rn = ROW_NUMBER() OVER (PARTITION BY RequestID ORDER BY ActionTimestamp DESC)
  FROM dbo.RequestActions
  WHERE Status = 'Open'
)
SELECT RequestID, ActionTimestamp /* , other columns */ 
  FROM MostRecentActions
  WHERE rn = 1;      
于 2012-06-15T21:24:20.557 回答
1

您可以在单个数据库查询中使用 LINQ to Entities 进行查询:

var openRequests = context.Requests
    .Where(r => r.RequestActions
        .OrderByDescending(ra => ra.ActionTimestamp)
        .Select(ra => ra.Status)
        .FirstOrDefault() == "Open")
    .ToList();

生成的 SQL 如下所示:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[SomeProp1] AS [SomeProp1], 
[Extent1].[SomeProp2] AS [SomeProp2], -- ...etc.
FROM  [dbo].[Requests] AS [Extent1]
CROSS APPLY  (SELECT TOP (1) [Project1].[Status] AS [Status]
    FROM ( SELECT 
           [Extent2].[Status] AS [Status], 
           [Extent2].[ActionTimestamp] AS [ActionTimestamp]
           FROM [dbo].[RequestActions] AS [Extent2]
           WHERE [Extent1].[Id] = [Extent2].[RequestId]
    ) AS [Project1]
    ORDER BY [Project1].[ActionTimestamp] DESC ) AS [Limit1]
WHERE N'Open' = [Limit1].[Status]

我不知道这是否是一个“好”和高性能的 SQL。

于 2012-06-16T00:28:05.467 回答