Home > Blockchain >  IDictionary extension not actually running while in LInq Query .Where()
IDictionary extension not actually running while in LInq Query .Where()

Time:06-02

I have an extension for an IDictionary that is meant to allow it to be case insensitive when doing a contains:

public static bool ContainsKeyIgnoreCase<TKey, TModel>(this IDictionary<TKey, TModel> dictionary, TKey key)
{
    bool? keyExists;

    var keyString = key as string;
    if (keyString != null)
    {
        keyExists =
            dictionary.Keys.OfType<string>()
            .Any(k => string.Equals(k, keyString, StringComparison.OrdinalIgnoreCase));
    }
    else
    {
        keyExists = dictionary.ContainsKey(key);
    }

    return keyExists ?? false;
}

But it never seems to run, hitting breakpoints or anything, while inside a .Where() :

public IEnumerable<TModel> PopulateIdsFromExistingRowsAndReturnNewRowsToCreate<TFromDBDto, TKey, TModel>(
    IEnumerable<TFromDBDto> fromDatabase,
    IDictionary<TKey, TModel> fromImport,
    Func<TFromDBDto, TKey> keySelector)
    where TFromDBDto : class, IGuidDataTransferObject
    where TModel : class, IModel<Guid>
{

    var groupedItems = fromDatabase
        .GroupBy(i => keySelector(i));
    var onlyKeys = groupedItems.Select(i => new { key = i.Key, value = i.First() });
    var filtered = onlyKeys.Where(i => fromImport.ContainsKeyIgnoreCase(i.key));
    var items = filtered.ToDictionary(i => i.key, i => i.value);

    var newItems = fromImport
        .Where(i => !items.ContainsKeyIgnoreCase(i.Key))
        .Select(i => i.Value);

    fromImport
        .Where(i => items.ContainsKeyIgnoreCase(i.Key))
        .ForEach(i => { i.Value.ID = items.AtKey(i.Key).ID; });

    return newItems;
}

However it will run if inside a for loop:

foreach(var keys in onlyKeys)
    {
        fromImport.ContainsKeyIgnoreCase(keys.key);
    }

Wondering if I'm doing something wrong or simply why it would refuse to actually run through the extension.

CodePudding user response:

Continuing on my comment, and as @Kirk Woll pointed out, this is an example of a "deferred execution". In simple words the execution of Where method is delayed until you would need that data.

In your case, you have following code:

    var newItems = fromImport
        .Where(i => !items.ContainsKeyIgnoreCase(i.Key))
        .Select(i => i.Value);

    fromImport
        .Where(i => items.ContainsKeyIgnoreCase(i.Key))
        .ForEach(i => { i.Value.ID = items.AtKey(i.Key).ID; });

    return newItems;

in this moment of execuion there is nothing that accessing the data itself, you do return it, but not interacting.

if you create and test this example (breakpoint is at return in where):

Dictionary<string, int> dict = new Dictionary<string, int>();
dict.Add("a", 0);
dict.Add("b", 0);
dict.Add("c", 0);

dict.Add("A", 0);
dict.Add("B", 0);
dict.Add("C", 0);

var newItems = dict
    .Where(i => {
        return !dict.ContainsKeyIgnoreCase(i.Key);
    })
    .Select(i => i.Value);

Thread.Sleep(10000);

Console.WriteLine(newItems.Count());

you will see that the breakpoint will not be hit until 10 seconds mark (loot at this screenshot: https://imgur.com/a/uPaAOvS)

As to you comment about breakpoint is never hit, make sure you return a reference to a enumerable rather than a copy, and if you actually interact with it.

As a crude solution, you could make this change to your code:


    var newItems = fromImport
        .Where(i => !items.ContainsKeyIgnoreCase(i.Key))
        .Select(i => i.Value)
        .ToList();

this will force the execution of your query, because data needs to be copied into new list

CodePudding user response:

There is nothing processing the query:

fromImport
    .Where(i => items.ContainsKeyIgnoreCase(i.Key))
    .ForEach(i => { i.Value.ID = items.AtKey(i.Key).ID; });

What is the definition of the ForEach() extenaion method? It might be that the ForEach() is returning an iterator (IEnumerable<>); you need to do something with it to process the query. For example:

var myStuff = fromImport
    .Where(i => items.ContainsKeyIgnoreCase(i.Key))
    .ForEach(i => { i.Value.ID = items.AtKey(i.Key).ID; })
    .ToList();  // <-- this will run the query
  • Related