简体   繁体   中英

Re-use parts of a linq query

We have a program that uses Linq-To-Sql and does a lot of similar queries on our tables. In particular, we have a timestamp column, and we pull the most recent timestamp to see if the table has changed since our last query.

System.Data.Linq.Binary ts;
ts = context.Documents
    .Select(r => r.m_Timestamp)
    .OrderByDescending(r => r)
    .FirstOrDefault();

We repeat this query often on different tables that's relevant for the current form/query whatever. What I would like to do is create a "function" or something that can repeat the last 3 lines of this query (and ideally would work on every table). Here's what I would like to write:

System.Data.Linq.Binary ts;
ts = context.Documents
    .GetMostRecentTimestamp();

But I have no idea how to create this "GetMostRecentTimestamp". Also, these queries are never this simple. They usually filter by the Customer, or by the current order, so a more valid query might be

ts = context.Documents
    .Where(r => r.CustomerID == ThisCustomerID)
    .GetMostRecentTiemstamp(); 

Any help? Thanks!


Update [Solution]

I selected Bala R's answer, here's the code updated so it compiles:

public static System.Data.Linq.Binary GetMostRecentTimestamp(this IQueryable<Data.Document> docs)
{
    return docs
        .Select(r => r.m_Timestamp)
        .OrderByDescending(r => r)
        .FirstOrDefault();
    }

The only drawback to this solution is that I will have to write this function for each table. I would have loved Tejs's answer, if it actually worked, but I'm not re-designing my database for it. Plus DateTime is a not a good way to do timestamps.


Update #2 (Not so fast)

While I can do a query such as Documents.Where(... ).GetMostRecentTimestamp(), this solution fails if I try to do an association based query such as MyCustomer.Orders.GetMostRecentTimestamp(), or MyCustomer.Orders.AsQueryable().GetMostRecentTimestamp();

This is actually pretty easy to do. You simply need to define an interface on the entities you wish to provide this for:

public class MyEntity : ITimestamp

Then, your extenstion method:

public static DateTime GetMostRecentTimestamp<T>(this IQueryable<T> queryable)
    where T : ITimestamp
{
     return queryable.Select(x => x.m_Timestamp)
            .OrderByDescending(r => r)
            .FirstOrDefault()
}

This is then useful on any entity that matches the interface:

context.Documents.GetMostRecentTimestamp()
context.SomeOtherEntity.GetMostRecentTimestamp()

How about an extension like this

public static DateTime GetMostRecentTimestamp (this IQueryable<Document> docs)
{
    return docs.Select(r => r.m_Timestamp)
               .OrderByDescending(r => r)
               .FirstOrDefault();
}

Hmm...

DateTime timeStamp1 = dataContext.Customers.Max(c => c.TimeStamp);
DateTime timeStamp2 = dataContext.Orders.Max(c => c.TimeStamp);
DateTime timeStamp3 = dataContext.Details.Max(c => c.TimeStamp);

I created a pair of extension methods that could help you out: ObjectWithMin and ObjectWithMax:

public static T ObjectWithMax<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
        where TResult : IComparable<TResult>
    {
        if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
        if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");

        var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();

        return elements.Aggregate(seed,
                                  (s, x) =>
                                  projection(x).CompareTo(s.Projection) >= 0
                                      ? new {Object = x, Projection = projection(x)}
                                      : s
            ).Object;
    }

public static T ObjectWithMin<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
        where TResult : IComparable<TResult>
    {
        if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
        if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");

        var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();

        return elements.Aggregate(seed,
                                  (s, x) =>
                                  projection(x).CompareTo(s.Projection) < 0
                                      ? new {Object = x, Projection = projection(x)}
                                      : s
            ).Object;
    }

These two are more efficient than OrderBy().FirstOrDefault() when used on in-memory collections; however they're unparseable by IQueryable providers. You'd use them something like this:

ts = context.Documents
    .Where(r => r.CustomerID == ThisCustomerID)
    .ObjectWithMax(r=>r.m_Timestamp);

ts is now the object having the most recent timestamp, so you don't need a second query to get it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM