Question

I'm trying to write a query in LINQ and, so far, I'm unable to make it work. If I've managed to ask the most obvious LINQ question in history then I apologize but I really do need some help with this one ...

Here is the gist of what I'm trying to do:

I have a class for a keyword:

class Keyword
{
    public string Name {get; set;}
}

I also have a class for a file:

class File
{
    public IList<Keyword> Keywords { get; set;}
}

Now, assuming I have a method to do a search for files by keyword(s):

IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
    // Let's say that Context.Files is a collection of File objects
    //   each of which contains a collection of associated keywords
    //   that may (or may not) match the keywords we get passed as 
    //   a parameter. This is where I need LINQ magic to happen.
    return Context.Files; // How do I select the files by the list of keywords?
}

I've seen examples of using Contains on the passed in list of keywords but that only seems to work for instances where the matching property is a scalar. In my case the matching property is another list of keywords.

In other words, this doesn't work:

IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
    return Context.Files.Where(x => keywords.Contains(x);
}

Anyone have any ideas? I really just need to find files that contain one or more keywords that match whatever is in the list of keywords passed in as a parameter. It's probably got an obvious solution but I can't see it.

Thanks in advance.

Was it helpful?

Solution

Do you want to find all File objects, for which any of the elements in Keywords are present in the collection of keywords you passed into the method?

Doing anything with the Keyword class inside your query is apparently a no-no. As your error suggests, it can only translate primitive types.

var names = keywords.Select(x => x.Name).ToList();

return Context.Files.Where(x => keywords.Select(y => y.Name).Intersect(names).Any());

OTHER TIPS

Maybe since you are having trouble wrapping your head around how to do this with Linq, you should start with something you can do. Some simple loops for example. Once you have that working, you can move on to Linq queries.

IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
    var foundFiles = new List<File>();

    foreach (File file in Context.Files)
    {
        foreach (string fileWord in file)
        {
            foreach (string keyword in keywords)
            {
                if (fileWord == keyword)
                {
                    foundFiles.Add(file);
                    break;
                }
            }
        }
    }

    return foundFiles;
}

I might build an index first, then query the index:

IDictionary<string, List<File>> BuildIndex(IEnumerable<File> files)
{
    var index = new Dictionary<string, List<File>>();

    foreach (File file in files)
    {
        foreach (string keyword in file.Keywords.Select(k => k.Name))
        {
            if (!index.ContainsKey(keyword))
                index[keyword] = new List<File>();
            index[keyword].Add(file);
        }
    }

    return index;
}

IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
    var index = BuildIndex(Context.Files);

    return keywords
        .Where(k => index.ContainsKey(k.Name))
        .SelectMany(k => index[k.Name])
        .Distinct()
        .ToList();
}

As I understand it, you're looking for all the files where any of their keywords is in the collection of keywords.

I'd write it like this:

IEnumerable<File> FilesThatContainsAnyKeyword(
        IEnumerable<File> files, // IQueryable<File>?
        IEnumerable<Keyword> keywords)
{
    var keywordSet = new HashSet<string>(keywords.Select(k => k.Name));
    return files.Where(f => 
        f.Keywords.Any(k => keywordSet.Contains(k.Name))
    );
}

Then call it:

IEnumerable<File> FindByKeywords(IEnumerable<Keyword> keywords)
{
    return FilesThatContainsAnyKeyword(Context.Files, keywords);
}

Since your keyword objects cannot be compared for equality directly, you have to compare them by identity (their Name).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top