0

I have a custom method for getting a string which is used with jqGrid for select lists for searching.

The required string format is ":All;value1:text1;value2:text2" I currently have the following method to achieve this in a reusable way:

public static string jqGridFilterSelectList<TSource>(this IQueryable<TSource> source,Expression<Func<TSource, string>> selector)
{
     return string.Join(";", source.Select(selector).Distinct().ToArray()); 
}

This works OK, but it requires the call to be like this:

string filterSelectList = service.GetAllUsers().jqGridFilterSelectList(x=>x.User + ":" + x.User);

I'm not very happy with having to use a selector like x=>x.User + ":" + x.User. I'd like to convert this into 2 overloads like this (pseudo code!!)

// case where we want the value and text of the select list to be the same
public static string jqGridFilterSelectList<TSource>(this IQueryable<TSource> source,Expression<Func<TSource, string>> selector)
{
     return string.Join(";", source.Select(selector + ":" + selector).Distinct().ToArray()); 
     //obviously this won't work, but is it possible in some other way while executing it on the database still? Also is it possible to insist the selector only contains 1 column.
}

//case where we want different text and value
public static string jqGridFilterSelectList<TSource>(this IQueryable<TSource> source,Expression<Func<TSource, string>> textSelector,Expression<Func<TSource, string>> valueSelector)
{
     return string.Join(";", source.Select(valueSelector + ":" + textSelector).Distinct().ToArray()); 
}

I suppose i could probably achieve this using dynamic linq, but i'm interested to know if this is possible.

Paul Creasey
  • 28,321
  • 10
  • 54
  • 90

2 Answers2

1

If I understand correctly, you've almost written it:

public static string jqGridFilterSelectList<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, string>> selector
)
{
     var compiled = selector.Compile();
     return string.Join(";", 
         source.Select(x => compiled(x) + ":" + compiled(x))
             .Distinct().ToArray()
     ); 
}

//case where we want different text and value
public static string jqGridFilterSelectList<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, string>> textSelector, 
    Expression<Func<TSource, string>> valueSelector
)
{
     return string.Join(";", 
         source.Select(x => valueSelector.Compile()(x) + ":" + textSelector.Compile()(x))
             .Distinct().ToArray()
     ); 
}

You just need to invoke your selector functions to get the values. Let me know if I misunderstand your question.

For the second question, is it possible to insist the selector only contains 1 column, the simple answer is no - the selector can be virtually any valid Func that takes TSource and returns string and there's no way to limit what can be done inside (unless you want to examine the expression tree provided, but it will be more complicated than the outcome, for sure).

EDIT: OK, so this is some kind of SQL LINQ provider, Entity Framework? So let's try a bit differently. I don't know whether it'll work, but let's try to fetch the data first and concatenate on objects:

public static string jqGridFilterSelectList<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, string>> selector
)
{
     var compiled = selector.Compile();
     return string.Join(";", 
         source.Distinct().AsEnumerable()
             .Select(x => compiled(x) + ":" + compiled(x))
             .ToArray()
     ); 
}

//case where we want different text and value
public static string jqGridFilterSelectList<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, string>> textSelector, 
    Expression<Func<TSource, string>> valueSelector
)
{
     return string.Join(";", 
         source.Distinct().AsEnumerable()
             .Select(x => valueSelector.Compile()(x) + ":" + textSelector.Compile()(x))
             .ToArray()
     ); 
}

The additional ToEnumerable call switches between database an in-process processing. Assuming that your selectors are deterministic and don't have any side effects, you can operate on already fetched TSource items.

NOtherDev
  • 9,542
  • 2
  • 36
  • 47
  • This does not work, when trying `selector(x)` gives the error "selector is a variable but is used like a method". I tried `x => Expression.Invoke(selector) + ":" + Expression.Invoke(selector)` but it throws `InvalidOperationException: Incorrect number of arguments supplied for lambda invocation` – Paul Creasey Mar 28 '11 at 15:59
  • @Paul Creasey, right, this is an `Expression`. So you need to compile it before invoking: `var myFunc = selector.Compile(); myFunc(x)` or inline: `selector.Compile()(x)`. Answer altered. – NOtherDev Mar 28 '11 at 16:13
  • Could not translate expression 'Table(Finance_RTC_MI_Data).Select(x => ((Invoke(Invoke(value(System.Func`1[System.Func`2[com.blah.lvis.Data.DataAccess.Finance_RTC_MI_Data,System.String]])),x) + ":") + Invoke(Invoke(value(System.Func`1[System.Func`2[com.blah.lvis.Data.DataAccess.Finance_RTC_MI_Data,System.String]])),x)))' into SQL and could not treat it as a local expression. – Paul Creasey Mar 28 '11 at 16:39
  • @Paul Creasey, I've done one more try (see edit), didn't know we're talking about SQL LINQ provider... – NOtherDev Mar 28 '11 at 17:14
  • @A, i've left the office now, will try this in the morning, i appreciate the effort. Should have mentioned it was a LinqToSql provider. – Paul Creasey Mar 28 '11 at 17:57
1

One more way to implement what you need is to use dataUrl and buildSelect parameters of the searchoptions.

In the case the value string in the form ":All;value1:text1;value2:text2" will not be built till it is really needed (till the first searching). When the user click on the search dialog the jqGrid get the data from the dataUrl. The server method can have very clear interface and return JSON representation of List<string>. On the client side you define buildSelect event handle which convert the the ["text1", "text2", ...] to the <select>. It's all. An example the usage of this ways you can find here. In comparing with the example you need only add additional option <option value="">All</option> in the select element.

Sometimes you should additionally use ajaxSelectOptions jqGrid parameter to customize the corresponding $.ajax request which get the data for the select list.

The advantages of the way:

  1. The server method providing the data for select list can have very clear interface and return just List.
  2. The size of data send from the server will be reduced because you don't send the "value1" and "text1" twice because of the values are the same as the text in your case.
  3. You can use the same method for editoptions in case of your grid use any editing method (inline, form or cell editing). In another implementation of buildSelect you should only skip inserting of <option value="">All</option> in the select list.
  4. The data for select element will be build only if there really needed: at the first usage of searching.
  5. The HTTP GET response from the server for select list can be cached automatically and if you need the same select data in another grid the previous server response can be get directly from the local browser cache.
Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • Thanks, I have considered this, and i might still try it, but i'm trying to minimize the number of http round trips involved, since the site is hosted in the UK but is used from all around the world and things can get a bit slow. – Paul Creasey Mar 28 '11 at 16:20
  • @Paul Creasey: It's a little the question of the taste. You can just try this. In general if you place the `Cache-Control: max-age:...` in the server response the server response can be get from the local cache for a long time, so it will be **no round trips**. I personally prefer to have good architecture. It some small performance issues will be you have many way to solve the problem with pure changes in the HTTP readers of the server response. – Oleg Mar 28 '11 at 16:33