141

Using linq, how can I retrieve a list of items where its list of attributes match another list?

Take this simple example and pseudo code:

List<Genres> listofGenres = new List<Genre>() { "action", "comedy" });   
var movies = _db.Movies.Where(p => p.Genres.Any() in listofGenres);
1
  • Pseudo code makes this question too vague to answer. For one, it's not clear how equality of Genre is defined. Commented Dec 4, 2021 at 13:59

7 Answers 7

252

Sounds like you want:

var movies = _db.Movies.Where(p => p.Genres.Intersect(listOfGenres).Any());
Sign up to request clarification or add additional context in comments.

8 Comments

i was trying to use this query for search box, it searches any character in Person_Name column, i got this error :'DbIntersectExpression requires arguments with compatible collection ResultTypes' so i tried .StartWith, .EndsWith, .Contains from here it works, but what can be done to use your query
@stom: We don't have nearly enough information to help you with that - you should ask a new question with a lot more context.
@JonSkeet I always use Contains method for these kind of queries. I was curious by seeing your answer and checked the internal implementation and found that Intersect uses Set. Can you tell me the performance difference between those two methods?
@Rebornx: Using Contains repeatedly ends up as an O(x*y) operation in time, but O(1) in space, where x is the size of the first collection and y is the size of the second. Using Intersect is O(x+y) in time but O(y) in space - it constructs a hashset from the second collection, which makes it quick to check for inclusion for any item from the first collection. See codeblog.jonskeet.uk/2010/12/30/… for details
@SteveBoniface: I wouldn't expect so, no. I'd expect the latter to be very slightly faster, as there's less indirection.
|
74

You can use a Contains query for this:

var movies = _db.Movies.Where(p => p.Genres.Any(x => listOfGenres.Contains(x));

Comments

9

If you use HashSet instead of List for listofGenres you can do:

var genres = new HashSet<Genre>() { "action", "comedy" };   
var movies = _db.Movies.Where(p => genres.Overlaps(p.Genres));

Comments

4

I guess this is also possible like this?

var movies = _db.Movies.TakeWhile(p => p.Genres.Any(x => listOfGenres.Contains(x));

Is "TakeWhile" worse than "Where" in sense of performance or clarity?

1 Comment

TakeWhile is a different function - it will stop iterating when it does not find a match.
2

Or like this

class Movie
{
  public string FilmName { get; set; }
  public string Genre { get; set; }
}

...

var listofGenres = new List<string> { "action", "comedy" };

var Movies = new List<Movie> {new Movie {Genre="action", FilmName="Film1"},
                new Movie {Genre="comedy", FilmName="Film2"},
                new Movie {Genre="comedy", FilmName="Film3"},
                new Movie {Genre="tragedy", FilmName="Film4"}};

var movies = Movies.Join(listofGenres, x => x.Genre, y => y, (x, y) => x).ToList();

Comments

2

If the Genre is an Entity and has its own Properties such as Title, use the following code:

var listofGenresName = new List<string> { "action", "comedy" };
var movies = _db.Movies.Where(p => p.Genres.Any(x => listofGenresName.Any(g=> g == x.Title)));

Comments

0

This is also a great use case for an extension method ContainsAny like this:

public static class IEnumerableExtensions
{
    /// <summary>
    /// Determines whether a sequence contains any of the specified items.
    /// </summary>
    /// <typeparam name="T">The type of the elements in the sequences.</typeparam>
    /// <param name="source">The sequence to search.</param>
    /// <param name="items">The sequence of items to search for in the source sequence.</param>
    /// <returns><c>true</c> if the source sequence contains any of the specified items; otherwise, <c>false</c>.</returns>
    /// <remarks>
    /// If either the source or items sequence is null, it will be treated as an empty sequence.
    /// This method uses a HashSet for efficient lookup of items.
    /// </remarks>
    public static bool ContainsAny<T>(this IEnumerable<T> source, IEnumerable<T> items)
    {
        source ??= Array.Empty<T>();
        items ??= Array.Empty<T>();

        var itemSet = new HashSet<T>(items);
        return source.Any(itemSet.Contains);
    }
}

And then you can use like this:

var dbMovies = new List<Movie>
{
    new Movie("Moana", new [] { "Animation", "Comedy" }),
    new Movie("Mulan", new [] { "Animation", "Family" }),
    new Movie("Brave", new [] { "Adventure", "Comedy" }),
};

var listOfGenres = new [] { "Comedy", "Western" };

var movies = dbMovies.Where(m => m.Genres.ContainsAny(listOfGenres));

Console.WriteLine(string.Join(", ", movies.Select(m => m.Title)));
// Moana, Brave

Demo in Dotnet Fiddle

Searching for a list in a list in a list is already confusing enough, and this helps abstract some of he parts away to make the code more readable.

See Also: How to use Linq to check if a list of strings contains any string in a list

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.