33

I've written some custom configuration collections, elements etc. Now, I'd like to do a simple Linq statement:

ServerDetails servers = ConfigurationManager.GetSection("serverDetails") as ServerDetails;
var server = from s in servers
             where s.Name == serverName
             select s;

I get the error:

Could not find an implementation of the query pattern for source type 'MyNamespace.ServerDetails'. 'Where' not found.

The ServerElement has two properties:

public class ServerElement : ConfigurationElement
{
    [ConfigurationProperty("ip")]
    public string IP
    {
        get { return (string)base["ip"]; }
        set { base["ip"] = value; }
    }

    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name
    {
        get { return (string)base["name"]; }
        set { base["name"] = value; }
    }
}

ServerDetails

public sealed class ServerDetails : ConfigurationSection
{
    [ConfigurationProperty("ServerCollection")]
    [ConfigurationCollection(typeof(ServerCollection), AddItemName = "add")]
    public ServerCollection ServerCollection
    {
        get { return this["ServerCollection"] as ServerCollection; }
    }
}

ServerCollection

public sealed class ServerCollection : ConfigurationElementCollection
{
    public void Add(ServerElement ServerElement)
    {
        this.BaseAdd(ServerElement);
    }

    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return new ServerElement();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((ServerElement)element).Name;
    }
}

Am I missing something? Do I need to add something in so that I can use Linq with a custom configuration element?

By the way, I have using System.Linq; defined as I'm using it else where within the same class.

Neil Knight
  • 47,437
  • 25
  • 129
  • 188

4 Answers4

41

Okay, given that it's all weakly typed, you'll need to either call Cast<> or OfType<> explicitly, or give an explicit type to the range variable. You'll also need to specify the ServerCollection property on your ServerDetails. For example:

ServerDetails servers = (ServerDetails) ConfigurationManager.GetSection("serverDetails");
var server = from ServerElement s in servers.ServerCollection
             where s.Name == serverName
             select s;
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    This is why I usually provide a `Children` property on `ConfigurationElementCollections` - strangely enough explicitly implementing `IEnumerable` does not do the trick. – Jonathan Dickinson Dec 07 '11 at 10:54
  • Hi Jon, firstly thanks for taking the time to look at my question. This answer is great, but I had to change the `from ServerDetails` to `from ServerElement` and it works :o) Thank you!! – Neil Knight Dec 07 '11 at 10:55
23

Using Brian Gideon's simple example of yield return in his IEnumerable<T> implementation, I was able to enumerate over my ConfigurationElementCollection.

It would look something like this (using the original question):

public sealed class ServerCollection : ConfigurationElementCollection,
    IEnumerable<ServerElement>
{
    ...

    public new IEnumerator<ServerElement> GetEnumerator()
    {
        foreach (var key in this.BaseGetAllKeys())
        {
            yield return (ServerElement)BaseGet(key);
        }
    }
}

While I was NOT getting the error:

Could not find an implementation of the query pattern for source type 'MyNamespace.ServerDetails'. 'Where' not found

...I was not able to iterate over my ConfigurationElementCollection using LINQ, either. This solution fixed my problem so that I could use LINQ to iteration over my collection.

Community
  • 1
  • 1
cat5dev
  • 1,317
  • 15
  • 12
4
 var server = ((ServerDetails) ConfigurationManager.GetSection("serverDetails")).
      ServerCollection.Cast<ServerElement>().FirstOrDefault(x => x.Name == serverName);
Jay Shah
  • 3,553
  • 1
  • 27
  • 26
  • 1
    Please try to provide a nice description about how your solution works. See: [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer). Thanks. – 4b0 Sep 09 '18 at 04:48
0

A very late answer, I would use this extension class to turn any ConfigurationElementCollection into an IEnumerable safely.

public static class ConfigurationElementCollectionExtension
{
    public static IEnumerable<T> ToEnumerable<T>(this ConfigurationElementCollection collection)
    {
        foreach (var element in collection)
        {
            if (element is T)
                yield return (T)element;

            yield return default;
        }
    }
}

Example usage below

ConfigurationManager
   .GetSection("serverDetails"))
   .ServerCollection
   .ToEnumerable<ServerElement>()
   .FirstOrDefault(x => x.Name == serverName);
Xiaoguo Ge
  • 2,177
  • 20
  • 26
  • 1
    I like @Xiaoguo Ge's solution, but have to add small fix. `yield return default;` should be in an else branch: `else yield return default;` – KOLRH Jul 28 '22 at 07:31