Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RavenDb static Index: query on child collection objects

I have a document structure like the following:

Employer => Positions => RequiredSkills

Employer has a collection of Position
Positions have a collection of RequiredSkill.
Required Skill consists of a skill (string) and a proficiency (enum).

If I use a dynamic index it seems to return company fine, however I want to use an index to populate MVC view models to return to the UI.

I'm really new to Raven so my apologies for doing anything stupid/unnecessary!

I've got the following mapping:

public class PositionSearch : AbstractIndexCreationTask<Employer>
    {
        public PositionSearch()
        {
            Map = employers =>
                  from employer in employers
                  from position in employer.Positions
                  select new
                      {
                          EmployerId = employer.Id,
                          EmployerName = employer.Name,
                          PositionId = position.Id,
                          PositionTitle = position.Title,
                          position.Location,
                          position.Description,
                          RequiredSkills = position.RequiredSkills
                      };

            StoreAllFields(FieldStorage.Yes);

            Index("RequiredSkills_Skill", FieldIndexing.Analyzed);
        }
    }

However when I try to execute the following query:

var results = session.Query<PositionSearchResultModel, PositionSearch>()
    .Customize(x => x.WaitForNonStaleResults())
    .Where(x=>x.RequiredSkills.Any(y=>y.Skill == "SkillName"))
    .ProjectFromIndexFieldsInto<PositionSearchResultModel>()
    .ToList();

I get the following error:

System.ArgumentException:
    The field 'RequiredSkills_Skill' is not indexed,
    cannot query on fields that are not indexed

Can anyone see what I'm doing wrong or suggest another approach for me please?

Thanks,

James

UPDATE my view model - Thanks :

public class PositionSearchResultModel
{
    public PositionSearchResultModel()
    {
        RequiredSkills = new HashSet<SkillProficiency>();
    }

    public string EmployerId { get; set; }
    public string EmployerName { get; set; }
    public string PositionId { get; set; }
    public string PositionTitle { get; set; }
    public string Location { get; set; }
    public string Description { get; set; }
    public ICollection<SkillProficiency> RequiredSkills { get; set; }
 }
like image 740
Jamez Avatar asked Feb 12 '13 14:02

Jamez


1 Answers

Because you want to do an analyzed search against the skill name, you need to isolate it as a separate index entry.

public class PositionSearch
    : AbstractIndexCreationTask<Employer, PositionSearchResultModel>
{
    public PositionSearch()
    {
        Map = employers =>
                from employer in employers
                from position in employer.Positions
                select new
                {
                    EmployerId = employer.Id,
                    EmployerName = employer.Name,
                    PositionId = position.Id,
                    PositionTitle = position.Title,
                    position.Location,
                    position.Description,
                    position.RequiredSkills,

                    // Isolate the search property into it's own value
                    SkillsSearch = position.RequiredSkills.Select(x => x.Skill)
                };

        // you could store all fields if you wanted, but the search field
        // doesn't need to be stored so that would be wasteful.
        Store(x => x.PositionId, FieldStorage.Yes);
        Store(x => x.PositionTitle, FieldStorage.Yes);
        Store(x => x.Location, FieldStorage.Yes);
        Store(x => x.Description, FieldStorage.Yes);
        Store(x => x.RequiredSkills, FieldStorage.Yes);

        // Any field you are going to use .Search() on should be analyzed.
        Index(x => x.SkillsSearch, FieldIndexing.Analyzed);
    }
}

Note that I specified the projection as the result of the index. This is syntactic sugar. It's not wrong to leave it off, but then you have to specify your search field using a string.

You'll also need to add the search field to your results class

public string[] SkillsSearch { get; set; }

It really doesn't matter what type it is. A string array or collection will do just fine. You could also use just a string or an object, because it's only the name that's relevant.

When you query against this index, use the .Search() method, like this:

var results = session.Query<PositionSearchResultModel, PositionSearch>()
    .Customize(x => x.WaitForNonStaleResults())  // only use this in testing
    .Search(x=> x.SkillsSearch, "SkillName")
    .ProjectFromIndexFieldsInto<PositionSearchResultModel>()  // AsProjection would also work
    .ToList();

Note that the only reason you have to store so many fields is because you want to project them. If you separated the positions into their own documents, you would have much smaller indexes and much less to project. Keep in mind that when you project, all of the fields in the original document are already there and come directly from the document store, rather than having to be copied into the index. So if your original documents more closely match your desired results, then there's less work to do.

like image 175
Matt Johnson-Pint Avatar answered Oct 26 '22 12:10

Matt Johnson-Pint