2011-08-18 29 views
5

Ứng dụng của tôi thường cần nhóm một nhóm, sau đó trả về hàng có giá trị lớn nhất cho nhóm đó. Điều này khá dễ thực hiện trong LINQ:LINQ - Viết một phương thức mở rộng để lấy hàng có giá trị lớn nhất cho mỗi nhóm

myTable.GroupBy(r => r.FieldToGroupBy) 
.Select(r => r.Max(s => s.FieldToMaximize)) 
.Join(
    myTable, 
    r => r, 
    r => r.FieldToMaximize, 
    (o, i) => i) 

Bây giờ giả sử tôi muốn tóm tắt điều này theo cách riêng của nó. Tôi đã cố gắng viết này:

public static IQueryable<TSource> 
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, TGroupKey>> groupKeySelector, 
    Expression<Func<TSource, TMaxKey>> maxKeySelector) 
    where TMaxKey : IComparable 
{ 
    return source 
     .GroupBy(groupKeySelector) 
     .Join(
      source, 
      g => g.Max(maxKeySelector), 
      r => maxKeySelector(r), 
       (o, i) => i); 
} 

Thật không may điều này không biên dịch: maxKeySelector là một biểu thức (vì vậy bạn không thể gọi nó về r, và bạn thậm chí không thể vượt qua nó để Max Vì vậy, tôi cố gắng viết lại,. làm maxKeySelector một chức năng chứ không phải là một biểu thức:.

public static IQueryable<TSource> 
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, TGroupKey>> groupKeySelector, 
    Func<TSource, TMaxKey> maxKeySelector) 
    where TMaxKey : IComparable 
{ 
    return source 
     .GroupBy(groupKeySelector) 
     .Join(
      source, 
      g => g.Max(maxKeySelector), 
      r => maxKeySelector(r), 
       (o, i) => i); 
} 

Bây giờ này biên soạn Nhưng nó không thành công trong thời gian chạy: "tình trạng quá tải không được hỗ trợ sử dụng cho các nhà khai thác truy vấn 'Max'" Đây là những gì tôi đang mắc kẹt trên: tôi cần phải tìm đúng cách để chuyển maxKeySelector vào Max().

Bất kỳ đề xuất nào? Tôi là chúng tôi ing LINQ to SQL, mà dường như tạo sự khác biệt.

Trả lời

4

Trước hết, tôi muốn chỉ ra rằng những gì bạn đang cố gắng làm là thậm chí dễ dàng hơn bạn nghĩ trong LINQ:

myTable.GroupBy(r => r.FieldToGroupBy) 
    .Select(g => g.OrderByDescending(r => r.FieldToMaximize).FirstOrDefault()) 

... mà nên làm cho cuộc sống của chúng ta một chút dễ dàng hơn cho phần thứ hai:

public static IQueryable<TSource> 
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, TGroupKey>> groupKeySelector, 
    Expression<Func<TSource, TMaxKey>> maxKeySelector) 
    where TMaxKey : IComparable 
{ 
    return source 
     .GroupBy(groupKeySelector) 
     .Select(g => g.AsQueryable().OrderBy(maxKeySelector).FirstOrDefault()); 
} 

Điều quan trọng là bằng cách làm cho nhóm của bạn một IQueryable, bạn mở một tập hợp mới các phương pháp LINQ có thể mất biểu thức thực tế chứ không phải là tham Func s. Điều này phải tương thích với hầu hết các nhà cung cấp LINQ tiêu chuẩn.

+0

Vâng, điều này chắc chắn ngắn gọn hơn, nhưng tiếc là nó không hoạt động. Tôi nhận được khá nhiều lỗi tương tự như trước: "Quá tải không được hỗ trợ được sử dụng cho toán tử truy vấn 'OrderBy'". – ctkrohn

+0

@ctkrohn: Nhà cung cấp LINQ nào bạn đang sử dụng? Nó làm việc tốt cho tôi trên LINQ to Entities .... và trên LINQ to Objects. – StriplingWarrior

+0

LINQ to SQL của một người này. – ctkrohn

4

Rất thú vị. Đôi khi "động" có thể khiến bạn mất nhiều thời gian hơn trong việc phát triển tuyệt đối và thực thi thời gian chạy hơn là giá trị (IMHO). Tuy nhiên, đây là đơn giản nhất:

public static IQueryable<Item> _GroupMaxs(this IQueryable<Item> list) 
{ 
    return list.GroupBy(x => x.Family) 
     .Select(g => g.OrderByDescending(x => x.Value).First()); 
} 

Và, đây là cách tiếp cận năng động nhất:

public static IQueryable<T> _GroupMaxs<T, TGroupCol, TValueCol> 
    (this IQueryable<T> list, string groupColName, string valueColName) 
{ 
    // (x => x.groupColName) 
    var _GroupByPropInfo = typeof(T).GetProperty(groupColName); 
    var _GroupByParameter = Expression.Parameter(typeof(T), "x"); 
    var _GroupByProperty = Expression 
      .Property(_GroupByParameter, _GroupByPropInfo); 
    var _GroupByLambda = Expression.Lambda<Func<T, TGroupCol>> 
     (_GroupByProperty, new ParameterExpression[] { _GroupByParameter }); 

    // (x => x.valueColName) 
    var _SelectParameter = Expression.Parameter(typeof(T), "x"); 
    var _SelectProperty = Expression 
      .Property(_SelectParameter, valueColName); 
    var _SelectLambda = Expression.Lambda<Func<T, TValueCol>> 
     (_SelectProperty, new ParameterExpression[] { _SelectParameter }); 

    // return list.GroupBy(x => x.groupColName) 
    // .Select(g => g.OrderByDescending(x => x.valueColName).First()); 
    return list.GroupBy(_GroupByLambda) 
     .Select(g => g.OrderByDescending(_SelectLambda.Compile()).First()); 
} 

Như bạn thấy, tôi đi trước Mở rộng phương pháp của tôi với dấu gạch dưới. Bạn không cần phải làm điều này, tất nhiên. Chỉ cần lấy ý tưởng chung và chạy với nó.

Bạn sẽ gọi nó là như thế này:

public class Item 
{ 
    public string Family { get; set; } 
    public int Value { get; set; } 
} 

foreach (Item item in _List 
     .AsQueryable()._GroupMaxs<Item, String, int>("Family", "Value")) 
    Console.WriteLine("{0}:{1}", item.Family, item.Value); 

Best of luck!

+0

Điều này có vẻ thông minh và hữu ích, nhưng có lẽ là quá mức cần thiết cho mục đích của tôi. Tôi sẽ đánh dấu kỹ thuật này trong trường hợp tôi phải tính đến bất kỳ phương pháp nào khác, nhưng bây giờ nó có lẽ dễ dàng hơn chỉ để viết mã trong dòng. – ctkrohn

+0

@ctkrohn, welp, xấu hổ về bạn vì đã yêu cầu phương pháp như vậy trong câu hỏi của bạn. Nếu tôi chọn, tôi cũng đã viết nó trực tuyến. Nhưng tôi không thấy một câu trả lời tao nhã hơn cho câu hỏi ban đầu của bạn về điều này. C'est la vie, tôi cho là vậy. –

+0

+1 để dành thời gian viết mã mà tôi quá lười để cung cấp. – StriplingWarrior

Các vấn đề liên quan