简体   繁体   中英

Filtering a collection by property in object in nested collection

I have three classes:

public class Error
{
    public List<ErrorOccurrenceDetails> Occurrences { get; set; }
}
public class ErrorOccurrenceDetails
{
    public Dictionary<DateTime, ErrorTime> Times { get; set; }
}
public class ErrorTime
{
    public string Version { get; set; }
}

There's an Error that has multiple Occurrences that happened in different Times . Each time can occur on a different Version .

I have a list of Error s. I need to remove errors which do not have provided version, for example if user only wants to see errors in version "1.0", other versions (such as "1.1", "1.2") need to be removed. However, if an error has occurrences on version "1.0" AND others, filter should delete only bad occurrences, not entire error.

I tried to do it this way:

private IList<Error> errors;

private void Filter(string filterVersion)
{
    var errorsToRemove = new Dictionary<int, ErrorOccurrenceDetails>();
    foreach (Error error in this.errors) 
    {
        int index = this.errors.IndexOf(error);
        var listOfSearchedItems = error.Occurences.ToList();
        listOfSearchedItems.RemoveAll(x => x.Times.Values.Any(y => y.Version != filterVersion));

        var errorOccurences = error.Occurences.Except(listOfSearchedItems).ToList();
        if (errorsToRemove.ContainsKey(index))
        {
            errorsToRemove[index].AddRange(errorOccurences);
        }
        else
        {
            errorsToRemove.Add(index, errorOccurences);
        }
    }
}

I'm trying to put all errors that need to be removed into errorsToRemove dictionary, where key is error index in this.errors , and value is a list of occurrences not on searched version. It kinda works, but sometimes doesn't remove bad versions and causes some correct versions to also be removed, so there is a hole I haven't noticed.

Sample error list in this fiddle: https://dotnetfiddle.net/Kxq50i

Based on Heinzi's answer I decided to use a much simpler approach. I think I wanted to avoid Collection was modified; enumeration operation may not execute Collection was modified; enumeration operation may not execute exception at all costs and started doing stupid things.

private IList<Error> errors;

private static void Filter(string filterVersion)
{
    for (int index = 0; index < errors.Count; index++) 
    {
        Error error = errors[index];
        foreach (var occurrence in error.Occurrences)
        {
            var toRemove = occurrence.Times.Keys.Where(key => occurrence.Times[key].Version != filterVersion).ToList();
            foreach (var key in toRemove)
            {
                occurrence.Times.Remove(key);
            }

            if (occurrence.Times.Count == 0)
            {
                 errors[index] = null; // not deleted to prevent index shift, null is hidden in UI
            }
        }
    }
}

The solution is updated in this fiddle .

The reason might be the following lines which might cause the items of the original error.Occurences to be removed:

var listOfSearchedItems = error.Occurences.ToList();
listOfSearchedItems.RemoveAll(x => x.Times.Values.Any(y => y.Version != filterVersion));

Try replacing those 2 lines with the one below:

var listOfSearchedItems = error.Occurences.Where(x => x.Times.Values.Any(y => y.Version == filterVersion)); // Note: Here we also replaced the "!=" with "=="

When using Linq, it is easier to think in terms of what to include rather than what you want to remove and write your code that way. For example:

private static IList<Error> Filter(string filterVersion)
{
    var filtered = errors.Where(error => error.Occurrences.Any(occurrence => occurrence.Times.Any(kvp => kvp.Value.Version == filterVersion)))
        .Select(error =>
        {
            return new Error
            {
                Occurrences = error.Occurrences
                    .Where(occurrence => occurrence.Times.Any(kvp => kvp.Value.Version == filterVersion))
                    .Select(occurrence => 
                    {
                        return new ErrorOccurrenceDetails
                        {
                            Times = new Dictionary<DateTime, ErrorTime>(occurrence.Times.Where(kvp => kvp.Value.Version == filterVersion))
                        };
                    })
                    .ToList()
            };
        })
        .ToList();
    return filtered;
}

Another possible benefit of this approach is that you are not modifying your original data, so you can apply different filters without having to re-read it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM