Tạo phương thức mở rộng Trình tạo dự đoán


8

Tôi có Lưới UI Kendo mà tôi hiện đang cho phép lọc trên nhiều cột. Tôi tự hỏi nếu có một cách tiếp cận khác để loại bỏ câu lệnh chuyển đổi bên ngoài?

Về cơ bản tôi muốn có thể tạo một phương thức mở rộng để tôi có thể lọc trên a IQueryable<T> và tôi muốn bỏ câu lệnh trường hợp bên ngoài để tôi không phải chuyển tên cột.

    private static IQueryable<Contact> FilterContactList(FilterDescriptor filter, IQueryable<Contact> contactList)
    {
        switch (filter.Member)
        {
            case "Name":
                switch (filter.Operator)
                {
                    case FilterOperator.StartsWith:
                        contactList = contactList.Where(w => w.Firstname.StartsWith(filter.Value.ToString()) || w.Lastname.StartsWith(filter.Value.ToString()) || (w.Firstname + " " + w.Lastname).StartsWith(filter.Value.ToString()));
                        break;
                    case FilterOperator.Contains:
                        contactList = contactList.Where(w => w.Firstname.Contains(filter.Value.ToString()) || w.Lastname.Contains(filter.Value.ToString()) || (w.Firstname + " " + w.Lastname).Contains( filter.Value.ToString()));
                        break;
                    case FilterOperator.IsEqualTo:
                        contactList = contactList.Where(w => w.Firstname == filter.Value.ToString() || w.Lastname == filter.Value.ToString() || (w.Firstname + " " + w.Lastname) == filter.Value.ToString());
                        break;
                }
                break;
            case "Company":
                switch (filter.Operator)
                {
                    case FilterOperator.StartsWith:
                        contactList = contactList.Where(w => w.Company.StartsWith(filter.Value.ToString()));
                        break;
                    case FilterOperator.Contains:
                        contactList = contactList.Where(w => w.Company.Contains(filter.Value.ToString()));
                        break;
                    case FilterOperator.IsEqualTo:
                        contactList = contactList.Where(w => w.Company == filter.Value.ToString());
                        break;
                }

                break;
        }
        return contactList;
    }

Một số thông tin bổ sung, tôi đang sử dụng NHibernate Linq. Ngoài ra, một vấn đề khác là cột "Tên" trên lưới của tôi thực sự là "Tên" + "" + "Họ" trên thực thể liên hệ của tôi. Chúng ta cũng có thể giả sử rằng tất cả các cột có thể lọc sẽ là các chuỗi.

EDIT Hãy nhớ điều này cần phải làm việc với NHibernate Linq và AST.


2
Bạn đã thấy Người xây dựng vị ngữ chưa?
Robert Harvey

@RobertHarvey - có nhưng tôi đã gác máy khi cố gắng giải quyết nhiều tên cột.
Rippo

Câu trả lời:


8

Trả lời câu hỏi cụ thể của bạn ,

private static IQueryable<Contact> FilterContactList(
    FilterDescriptor filter,
    IQueryable<Contact> contactList,
    Func<Contact, IEnumerable<string>> selector,
    Predicate<string> predicate)
{
    return from contact in contactList
           where selector(contract).Any(predicate)
           select contact;
}

Trong trường hợp "Tên", bạn gọi nó là;

FilterContactList(
    filter,
    contactList,
    (contact) => new []
        {
            contact.FirstName,
            contact.LastName,
            contact.FirstName + " " + contact.LastName
        },
    string.StartWith);

Bạn nên thêm một quá tải như,

private static IQueryable<Contact> FilterContactList(
    FilterDescriptor filter,
    IQueryable<Contact> contactList,
    Func<Contact, string> selector,
    Predicate<string> predicate)
{
    return from contact in contactList
           where predicate(selector(contract))
           select contact;
}

Vì vậy, bạn có thể gọi nó như thế này cho trường "Công ty".

FilterContactList(
    filter,
    contactList,
    (contact) => contact.Company,
    string.StartWith);

Điều này ngăn chặn chi phí buộc người gọi phải tạo một mảng khi họ chỉ có ý định chọn một Trường / Thuộc tính.

Những gì bạn có thể sau là một cái gì đó như sau

Để loại bỏ logic đó hoàn toàn xung quanh việc xác định selectorpredicatecần thêm thông tin về cách xây dựng bộ lọc. Nếu bộ lọc có thể nên có các thuộc tính selectorpredicatenhư là FilterContactList để sử dụng được xây dựng tự động.

Mở rộng ra một chút,

public class FilterDescriptor
{
    public FilterDescriptor(
        string columnName,
        FilterOperator filterOperator,
        string value)
    {
        switch (columnName)
        {
            case "Name":
                Selector = contact => new []
                               {
                                   contact.FirstName,
                                   contact.LastName,
                                   contact.FirstName + " " + contact.LastName
                               };
                break;
            default :
                // some code that uses reflection, avoids having
                // a case for every column name

                // Retrieve the public instance property of a matching name
                // (case sensetive) and its type is string.
                var property = typeof(Contact)
                    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(prop =>
                        string.Equals(prop.Name, columnName) &&
                        prop.PropertyType == typeof(string));

                if (property == null)
                {
                    throw new InvalidOperationException(
                        "Column name does not exist");
                }

                Selector = contact => new[]
                {
                    (string)property.GetValue(contact, null)
                };
                break;
        }

        switch (filterOperator)
        {
            case FilterOperator.StartsWith:
                Predicate = s => s.StartsWith(filter.Value);
                break;
            case FilterOperator.Contains:
                Predicate = s => s.Contains(filter.Value);
                break;
            case FilterOperator.IsEqualTo:
                Predicate = s => s.Equals(filter.Value);
                break;
        }
    }

    public Func<Contact, IEnumerable<string>> Selector { get; private set; }
    public Func<string, bool> Predicate { get; private set; }
}

FilterContactListSau đó bạn sẽ trở thành

private static IQueryable<Contact> FilterContactList(
    FilterDescriptor filter,
    IQueryable<Contact> contactList)
{
    return from contact in contactList
           where filter.Selector(contract).Any(filter.Predicate)
           select contact;
}

@Rippo mã được cập nhật, rõ ràng bạn cần giá trị mà chúng tôi đang tìm kiếm!
M Afifi

Thật thú vị, có vẻ như nó không chơi bóng ... Không thể phân tích biểu thức 'Gọi (giá trị (System.Func 2[Domain.Model.Entities.Contact,System.Collections.Generic.IEnumerable1 [System.String]]), liên hệ) .Any (value (System.Func`2 [System.String, System .Boolean])) ': Đối tượng của loại' System.Linq.Expressions.ConstantExpression 'không thể được chuyển đổi thành loại' System.Linq.Expressions.LambdaExpression '. Nếu bạn đã cố gắng vượt qua một đại biểu thay vì LambdaExpression, điều này không được hỗ trợ vì các đại biểu không phải là biểu thức có thể phân tích cú pháp.
Rippo

@Rippo bạn có thể bao gồm mã đằng sau FilterDescriptor và theo dõi ngăn xếp không?
M Afifi

Bộ mô tả bộ lọc là từ Kendo docs.kendoui.com/api/wrappers/aspnet-mvc/Kendo.Mvc/ Kẻ
Rippo

Ngăn xếp đầy đủ và mã gọi: gist.github.com/4181453
Rippo

1

Tôi nghĩ rằng một cách đơn giản để làm điều này sẽ là tạo một bản đồ tên tài sản cho Func's:

ví dụ

private static Dictionary<string, Func<Contact, IEnumerable<string>>> propertyLookup = new Dictionary<string, Func<Contact, IEnumerable<string>>>();

static ClassName() 
{
   propertyLookup["Name"] = c => new [] { c.FirstName, c.LastName, c.FirstName + " " c.LastName };
   propertyLookup["Company"] = c => new [] { c.Company }; 
}

Và thay đổi mã của bạn thành:

 var propertyFunc = propertyLookup(filter.Member);

 case FilterOperator.StartsWith:
          contactList = contactList.Where(c => propertyFunc(c).Any(s => s.StartsWith(filter.Value));

Bạn cũng có thể loại bỏ hoàn toàn công tắc bằng cách tạo một tra cứu cho chức năng khớp:

matchFuncLookup[FilterOperator.StartsWith] = (c, f) => c.StartsWith(f);
matchFuncLookup[FilterOperator.Contains] = (c, f) => c.Contains(f);

var matchFunc = matchFuncLookup[filter.Operator];

contactList = contactList.Where(c => propertyFunc(c).Any(s => matchFunc(s, filter.Value));

Vì vậy, để kết hợp tất cả lại với nhau:

public class ClassName
{
    private static readonly Dictionary<string, Func<Contact, IEnumerable<string>>> PropertyLookup
        = new Dictionary<string, Func<Contact, IEnumerable<string>>>();
    private static readonly Dictionary<FilterOperator, Func<string, string, bool>> MatchFuncLookup
        = new Dictionary<FilterOperator, Func<string, string, bool>>();

    static ClassName()
    {
        PropertyLookup["Name"] = c => new[] { c.FirstName, c.LastName, c.FirstName + " " + c.LastName };
        PropertyLookup["Company"] = c => new[] { c.Company };
        MatchFuncLookup[FilterOperator.StartsWith] = (c, f) => c.StartsWith(f);
        MatchFuncLookup[FilterOperator.Contains] = (c, f) => c.Contains(f);
        MatchFuncLookup[FilterOperator.IsEqualTo] = (c, f) => c == f;
    }

    private static IQueryable<Contact> FilterContactList(FilterDescriptor filter, IQueryable<Contact> contactList)
    {
        var propertyLookup = PropertyLookup[filter.Member];
        var matchFunc = MatchFuncLookup[filter.Operator];
        return contactList.Where(c => propertyLookup(c).Any(v => matchFunc(v, filter.Value)));
    }
} 

NB - Không phải là không cần thiết để kiểm tra c.FirstName nếu bạn cũng đang kiểm tra (c.FirstName + "" c.LastName)?


Đọc lại câu trả lời của @ MAfifi, phương pháp này tương tự - chỉ được thực hiện bằng cách sử dụng lambda với tra cứu thay vì các lớp và các câu lệnh chuyển đổi. Ưu điểm chính của phương pháp tra cứu qua chuyển đổi là việc thêm các hàm mới hoặc các cột yêu cầu thay đổi mã dễ dàng hơn - và nó cũng có thể mở rộng hơn (tất cả không phải được định nghĩa trong một lớp).
Brian Flynn

Cảm ơn vì điều này, tôi đã thử NHƯNG đã gặp phải lỗi sau: System.InvalidCastException Unable to cast object of type 'NHibernate.Hql.Ast.HqlParameter' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.
Rippo

Tôi không quen thuộc lắm với NHibernate, nhưng có vẻ như nó gặp khó khăn khi xử lý mệnh đề where phức tạp hơn. Bạn có thể thử sửa đổi truy vấn thành: contactList.Select (c => new {Contact = c, Values ​​= propertyLookup (c)}) .Where (cv => cv.Values.Any (v => matchFunc (v, bộ lọc .Value) .Chọn (cv => cv. Liên hệ);
Brian Flynn

xin lỗi lỗi đánh máy trong truy vấn đó: contactList.Select (c => new {Contact = c, Values ​​= propertyLookup (c)}) .Where (cv => cv.Values.Any (v => matchFunc (v, filter.Value) )). Chọn (cv => cv.Contact);
Brian Flynn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.