Rendre NameValueCollection accessible à la requête LINQ

Comment rendre NameValueCollection accessible à L'opérateur de requête LINQ tel que where, join, groupby?

J'ai essayé le ci-dessous:

private NameValueCollection RequestFields()
{
    NameValueCollection nvc = new NameValueCollection()
                                  {
                                      {"emailOption: blah Blah", "true"},
                                      {"emailOption: blah Blah2", "false"},
                                      {"nothing", "false"},
                                      {"nothinger", "true"}
                                  };
    return nvc;

}

public void GetSelectedEmail()
{
    NameValueCollection nvc = RequestFields();
    IQueryable queryable = nvc.AsQueryable();
}

Mais j'ai eu une ArgumentException me disant que la source n'est pas IEnumerable .

53
demandé sur Matteo Mosca 2008-12-24 11:28:47

7 réponses

Vous devez "soulever" le IEnumerable non générique à un IEnumerable<string>. Il a été suggéré d'utiliser OfType mais c'est une méthode de filtrage. Ce que vous faites est l'équivalent d'un cast, pour lequel il y a l'opérateur Cast:

var fields = RequestFields().Cast<string>();

Comme Frans l'a souligné, cela ne donne accès qu'aux clés. Vous devrez toujours indexer dans la collection pour les valeurs. Voici une méthode d'extension pour extraire KeyValuePair s du NameValueCollection:

public static IEnumerable<KeyValuePair<string, string>> ToPairs(this NameValueCollection collection)
{
    if(collection == null)
    {
        throw new ArgumentNullException("collection");
    }

    return collection.Cast<string>().Select(key => new KeyValuePair<string, string>(key, collection[key]));
}

Edit:, En réponse à @Ruben Demande de Bartelink, voici comment accéder à l'ensemble complet des valeurs pour chaque clé en utilisant ToLookup:

public static ILookup<string, string> ToLookup(this NameValueCollection collection)
{
    if(collection == null)
    {
        throw new ArgumentNullException("collection");
    }

    var pairs =
        from key in collection.Cast<String>()
        from value in collection.GetValues(key)
        select new { key, value };

    return pairs.ToLookup(pair => pair.key, pair => pair.value);
}
83
répondu Bryan Watts 2012-10-02 20:55:46

AsQueryable doit prendre un IEnumerable<T>, un générique. NameValueCollection implémente IEnumerable, ce qui est différent.

Au lieu de ceci:

{
    NameValueCollection nvc = RequestFields();
    IQueryable queryable = nvc.AsQueryable();
}

Essayez OfType (Il accepte l'interface non générique)

{
    NameValueCollection nvc = RequestFields();
    IEnumerable<string> canBeQueried = nvc.OfType<string>();
    IEnumerable<string> query =
       canBeQueried.Where(s => s.StartsWith("abc"));
}
9
répondu Amy B 2012-10-02 21:34:47

Un dictionnaire est probablement plus proche de ce que vous voulez utiliser car il remplira plus de rôles que NameValueCollection remplit. C'est une variante de la solution de Bryan Watts:

public static class CollectionExtensions
{
    public static IDictionary<string, string> ToDictionary(this NameValueCollection source)
    {
        return source.Cast<string>().Select(s => new { Key = s, Value = source[s] }).ToDictionary(p => p.Key, p => p.Value); 
    }
}
8
répondu Orion Adrian 2010-06-07 15:21:26

Je sais que je suis en retard à la fête mais je voulais juste ajouter ma réponse qui n'implique pas la méthode d'extension .Cast mais utilise plutôt la propriété AllKeys:

var fields = RequestFields().AllKeys;

Cela permettrait la méthode d'extension suivante:

public static IEnumerable<KeyValuePair<string, string>> ToPairs(this NameValueCollection collection)
{
    if(collection == null)
    {
        throw new ArgumentNullException("collection");
    }

    return collection.AllKeys.Select(key => new KeyValuePair<string, string>(key, collection[key]));
}

J'espère que cela aidera les futurs visiteurs

6
répondu NinjaNye 2013-09-17 16:07:58

Le problème est que la collection implémente IEnumerable (par opposition à IEnumerable<T>) et l'énumération de la collection renvoie les clés, pas les paires.

Si j'étais vous, j'utiliserais un Dictionary<string, string> qui est énumérable et peut être utilisé avec LINQ.

4
répondu Frans Bouma 2016-06-24 00:17:57

Pour moi, la variante ToLookup de la réponse de @Bryan Watts (+1'D) représente de loin l'approche la plus claire pour l'utiliser en lecture seule.

Pour mon cas d'utilisation, je manipule une chaîne de requête à utiliser avec Linq2Rest et j'ai aussi besoin de tout retourner en NameValueCollection à la fin, donc j'ai un ensemble de méthodes d'extension pour {[3] } qui offrent des opérations plus granulaires (pour fonctionner à la fois par nom de paramètre (AsEnumerable) et par ToNameValueCollection (de l'une ou l'autre représentation)).

Exemple de consommation:

public static NameValueCollection WithoutPagingOperators( this NameValueCollection that )
{
    return that.AsEnumerable()
        .Where( @param => @param.Key != OdataParameters.Skip 
          && @param.Key != OdataParameters.Top )
        .ToNameValueCollection();
}

Code:

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

public static class NamedValueCollectionExtensions
{
    public static IEnumerable<KeyValuePair<string, string[]>> AsEnumerable( this NameValueCollection that )
    {
        return that
            .Cast<string>() // doesn't implement IEnumerable<T>, but does implement IEnumerable
            .Select( ( item, index ) => // enable indexing by integer rather than string
                new KeyValuePair<string, string[]>( item, that.GetValues( index ) ) ); // if you use the indexer or GetValue it flattens multiple values for a key, Joining them with a ',' which we don't want
    }

    public static IEnumerable<KeyValuePair<string, string>> AsKeyValuePairs( this IEnumerable<KeyValuePair<string, string[]>> that )
    {
        return that
            .SelectMany( item =>
                item.Value.Select( value =>
                    new KeyValuePair<string, string>( item.Key, value ) ) );
    }

    public static NameValueCollection ToNameValueCollection( this IEnumerable<KeyValuePair<string, string[]>> that )
    {
        return that.AsKeyValuePairs().ToNameValueCollection();
    }

    public static NameValueCollection ToNameValueCollection( this IEnumerable<KeyValuePair<string, string>> that )
    {
        var result = new NameValueCollection();
        foreach ( KeyValuePair<string, string> item in that )
            result.Add( item.Key, item.Value );
        return result;
    }
}
3
répondu Ruben Bartelink 2012-10-03 13:01:53

Je ne vois pas vraiment pourquoi quelqu'un aurait besoin d'ajouter une méthode d'extension.
Voici quelques différentes façons de le faire dans VB.NET. il comprend 4 formes intermédiaires différentes de IEnumerable: Array, Tuple, Anonymous et KeyValuePair. Pour l'équivalent C# aller au convertisseur.telerik dot com et le convertir.

Dim nvc As New NameValueCollection() From {{"E", "55"}, {"A", "11"}, {"D", "44"}, {"C", "33"}, {"G", "66"}, {"B", "22"}}

Dim dictStrings As Dictionary(Of String, String) = nvc.Cast(Of String).ToDictionary(Function(key) key, Function(key) nvc(key))
Dim Ints2Chars__ As Dictionary(Of Integer, Char) = nvc.Cast(Of Object).ToDictionary(Function(key) CInt(nvc(CStr(key))), Function(key) CChar(key))

Dim arrEnumerable__ = From x In nvc.Cast(Of String) Select {x, nvc(x)}
Dim tupleEnumerable = From x In nvc.Cast(Of String) Select Tuple.Create(x, nvc(x))
Dim anonEnumerable_ = From X In nvc.Cast(Of String) Select New With {X, .Y = nvc(X)}
Dim kvpEnumerable__ = From x In nvc.Cast(Of String) Select New KeyValuePair(Of String, String)(x, nvc(x))

Dim anonQuery = From anon In anonEnumerable_ Let n = CInt(anon.Y) Order By n Where n > 30 Select New With {.num = n, .val = anon.X}
Dim dictQuery = anonQuery.ToDictionary(Of Integer, String)(Function(o) o.num, Function(o) o.val)


Dim dictArray_ = arrEnumerable__.ToDictionary(Function(x) x(0), Function(x) x(1))
Dim dictTuples = tupleEnumerable.ToDictionary(Function(tuple) tuple.Item1, Function(tuple) tuple.Item2)
Dim dictAnon__ = anonEnumerable_.ToDictionary(Function(anon) anon.X, Function(anon) anon.Y)
Dim dictKVPrs_ = kvpEnumerable__.ToDictionary(Function(kvp) kvp.Key, Function(kvp) kvp.Value)
1
répondu Derek Ziemba 2016-01-13 04:30:19