I have a situation where I want to do a query by two fields, I thought this would be pretty simple, but it is proving a great deal more difficult than I thought.
This is a rough look at the document being queried...
class Item {
string Id { get; set; }
string Name { get; set; }
Origin Origin { get; set; }
}
class Origin {
string Id { get; set; }
string Name { get; set; }
}
There are other fields, of course. I am just listing the relevant ones. So my Index looks like this.
public class Item__ByName : AbstractIndexCreationTask<Models.Items.Item, Item__ByName.Result> {
public class Result {
public string Id { get; set; }
public string Name { get; set; }
public string Origin { get; set; }
}
public Item__ByName() {
Map = items => from item in items
select new Result {
Id = item.Id,
Name = item.Name,
Origin = item.Origin.Name
};
Index(i => i.Name, FieldIndexing.Analyzed);
Index(i => i.Origin, FieldIndexing.Analyzed);
}
}
And then I attempt to query it like this...
var results = RavenSession
.Query<Models.Items.Item, Indexes.Item__ByName>()
.Where(n => n.Name == name)
.Where(n => n.Origin.Name == origin)
.ToList();
but it keeps telling me that "Origin_Name" is not defined. I am really confused about why it is saying this, when I've very clearly defined it.
A couple of things:
Index entry names cannot be nested. Even though the document has structure, the index is flat. To compensate for this, Raven uses the convention of replacing periods with underscores. So in your index definition, you have to name your field Origin_Name to match.
Origin_Name = item.Origin.Name
There's hardly ever a need to include the Id in the index map. It is handled differently behind the scenes, so it's included by default.
You don't need a Result class in this scenario, because you aren't storing these fields or using them in a map/reduce.
If you plan on doing a partial match, or a search, then having the fields analyzed is fine. If you are only doing a whole string match, then you shouldn't analyze the fields.
You should combine your Where queries into a single clause:
.Where(n => n.Name == name && n.Origin.Name == origin)
Alternatively, you can use the Intersection Queries feature:
.Where(n => n.Name == name)
.Intersect()
.Where(n => n.Origin.Name == origin)
This particular query is simple enough that if you wanted you could omit the static index and Raven would create one for you automatically.
You can also use .And() and .Or() between two where's especially when you use advance query with LuceneQuery Object
Related
I am kind of stuck on using the include with a RavenDB Transformer. Say I have the following document classes:
public class Processor
{
public string Id { get; set; }
// other properties
}
public class Job
{
public string Id { get; set; }
public string ProcessorId { get; set; }
// other properties
}
Hers is my view model:
public class ProcessorStatsViewModel
{
public string Id { get; set; }
public int JobCount { get; set; }
// other properties
}
In my tranformer I would like to query the Processors Document Store and do an include on the Jobs Store looking for every job with a matching Processor ID. All of the search results I found describe how to do this when the Processor class has the list of JobId's. Is there a way to do this in RavenDB?
The transformer I would like could look something like:
public Processors_StatsViewModel()
{
TransformerResults = procs =>
from p in procs
let jobs = Include<Jobs>(p.Id) // how can i specify something like where p.Id == j.ProcessorId ?
select new
{
p.Id
JobCount = jobs.Count
// other stuff
}
}
All of the Transformer LoadDocument, Include, and Recurse methods expect the class being queried to have a list reference ID's but in my case in need the opposite.
Is this something I can even do in RavenDB or am I missing something?
You can not do what you want to do with only a transformer and your current domain model. If the processors did indeed know about its jobs you could do this with a transformer kind of like the one you have.
However, you can achieve something similar with a Map/Reduce index and then a Transformer over the result of the Map/Reduce index. It all depends on what "other stuff" you want to present, but this is a way to get all Processes and its job count and then adding more information with a transformer:
Map/Reduce index to get job count by processor:
public class Jobs_ByProcessor : AbstractIndexCreationTask<Job, Jobs_ByProcessor.ReduceResult>
{
public class ReduceResult
{
public string ProcessorId { get; set; }
public int JobCount { get; set; }
}
public Jobs_ByProcessor()
{
Map = jobs => from job in jobs
select new ReduceResult
{
ProcessorId = job.ProcessorId,
JobCount = 1
};
Reduce = results => from result in results
group result by result.ProcessorId
into g
select new
{
ProcessorId = g.Key,
JobCount = g.Sum(x => x.JobCount)
};
}
}
Transformer:
public class ProcessorJobTransformer : AbstractTransformerCreationTask<Jobs_ByProcessor.ReduceResult>
{
public ProcessorJobTransformer()
{
TransformResults = results => from result in results
let processor = LoadDocument<Processor>(result.ProcessorId)
select new
{
Id = result.ProcessorId,
Name = processor.Name,
JobCount = result.JobCount
};
}
}
This would give you a result like this:
Id and JobCount comes from the Reduce result of the index and the Name comes from the Transformer (via LoadDocument).
However, if you need this results but more information from the Job document, you might have to go a different route entirely.
Hope this helps!
Given that I have the following structure (unnecessary details stripped out)
public class Product {
public Guid Id { get; set; }
public string Name { get; set; }
public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer {
public Guid Id { get; set; }
public string Name { get; set; }
}
If I have a lot of these kind of products stored in raven and I want to index them by manufacturer id (or maybe some other things as well) I'd make an index such as this (of course in real life this index also contains some other information as well...)
public class ProductManufacturerIndex : AbstractIndexCreationTask<Product> {
public ProductManufacturerIndex() {
Map = products => from product in products
select new {
Manufacturer_Id = product.Manufacturer.Id,
};
}
}
My question here is, why do I need to name my field Manufacturer_Id? If I do not name it Manufacturer_Id I get exceptions when attempting to query my index since the manufacturer id column is not indexed.
Basically, why can't I do this? (Which would be my first guess)
public class ProductManufacturerIndex : AbstractIndexCreationTask<Product> {
public ProductManufacturerIndex() {
Map = products => from product in products
select new {
product.Manufacturer.Id,
};
}
}
There is a naming convention that RavenDB uses. If you aren't naming your fields properly, it doesn't know how to map things.
In this case, the second index you use has a property of Id, but RavenDB has no way of knowing that you mapped the Manufacturer's id, and not the root id.
That is why we have this convention. You can change it if you really want to, but it is generally not recommended.
I have the following Map / Transform
public class PositionSearch : AbstractIndexCreationTask<Employer>
{
public PositionSearch()
{
Map = employers =>
from employer in employers
from position in employer.Positions
select new
{
EmployerName = employer.Name,
SearchSkills = position.RequiredSkills
.Select(x => x.Skill)
};
TransformResults = (database, results) =>
from result in results
from position in result.Positions
select new
{
EmployerId = result.Id,
EmployerName = result.Name,
PositionId = position.Id,
PositionTitle = position.Title,
RequiredSkills = position.RequiredSkills
.Select(x => new { x.Skill, x.Proficiency })
};
// Any field you are going to use .Search() on should be analyzed.
Index("SearchSkills", FieldIndexing.Analyzed);
}
}
I have an employer object with two positions, each with a single skill, "NH" and "MVC"
When I execute the following query I'm getting two position results returned, when I expected one.
Can anyone tell me why this is behaving this way? I've got a feeling it's something to do with a join i'm performing, but I'm not sure.
using (var session = DocumentStore.OpenSession())
{
var results = session.Query<PositionSearchResultModel, PositionSearch>()
.Customize(x => x.WaitForNonStaleResults())
.Search(x => x.SearchSkills, "NH")
.OfType<PositionSearchResultModel>().ToList();
Assert.AreEqual(1, results.Count());
}
I'm wanting to use transform so that I can access the Temp-Index-Score meta data for ordering, I've been unable to access the meta data without a transform so far.
You are indexing the Employer document. The search found a document that contained the skill in question, and then you asked to transform the document.
The way you had it before is projecting from the index, which is the only way you are going to get the specific position found as part of your results.
I really think you would be happier with Position as it's own document...
public class Employer
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Position
{
public string Id { get; set; }
public string EmployerId { get; set; }
public string Title { get; set; }
public string Location { get; set; }
public ICollection<SkillProficiency> RequiredSkills { get; set; }
}
This may seem more relational in thinking, but it works just fine in RavenDB, and it will be much easier to query than what you are doing now.
I have a RavenDB mvc applicaton that has a document entity called Member. Each Member document has a list of users that are considered administrators. Only they can view and manage that Member document. On one of the pages I have a member search and have created an index to assist in the search.
public class Members_ByName : AbstractIndexCreationTask<Member>
{
public Members_ByName()
{
Map = members => from member in members select new {member.Title};
Indexes.Add(x => x.Title, FieldIndexing.Analyzed);
Sort(x => x.Title, SortOptions.String);
}
}
public class UserReference
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Member
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<UserReference> Administrators { get; set; }
}
Since the user can only view/manage Member documents where they are an Administrator when I do the following to get the members
RavenQueryStatistics stats;
var query = RavenSession.Query<Member, Members_ByName>().Statistics(out stats);
query = query.Where(x => x.Title.StartsWith("anything"));
query = query.Where(x => x.Administrators.Any(y => y.Id == CurrentUser.Id));
var list = query.OrderBy(x => x.Title).Paging(CurrentPage, Configuration.DefaultPage, CurrentPageSize).ToList();
When the above code runs I get "The field 'Administrators_Id' is not indexed, cannot query on fields that are not indexed" which I understand but every thing I have attempted to get Administrator's Id in the index has not worked and not sure how to make it work at this point.
Try this:
Map = members => from member in members
select new {member.Title, Administrators_Id = members.Administrators.Select(x=>x.Id)};
I have an Class that is named Show one of the properties "Country" is a reference to another table.
Show Class
public class Show
{
public virtual int ID { get; set; }
public virtual Country CountryOrigin { get; set; }
public virtual string EnglishName { get; set; }
}
Country Class
public class Country
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
}
I have it all mapped and working, but now I am wanting to get more specific results. I have used the criteria api to get all the data and sort it, but now I only want to get shows based on country name. Here is what I thought would work, but apprently doesn't.
public IList<Show> AllShowsByCountry(string countryName)
{
IList<Show> shows;
shows = _session.CreateCriteria(typeof(Show))
.Add(Restrictions.Eq("CountryOrigin.Name", "China" ))
.AddOrder(Order.Asc("EnglishName"))
.List<Show>();
return shows;
}
I was thinking that the first part of the restriction might work similar to HQL and you can use objects.
1) The question I guess is am I mis-understanding how HQL works or criteria or both?
2) Also how would you do this properly using criteria?
Update
Here is the error I am getting
could not resolve property: CountryOrigin.Name of: Entities.Show
For Criteria, use the following:
_session.CreateCriteria<Show>()
.CreateAlias("CountryOrigin", "country")
.Add(Restrictions.Eq("country.Name", countryName))
.AddOrder(Order.Asc("EnglishName"))
.List<Show>();
Of course HQL is easier when you are not constructing a dynamic query (search):
_session.CreateQuery(
#"
from Show
where CountryOrigin.Name = :countryName
order by EnglishName
")
.SetParameter("countryName", countryName)
.List<Show>();
And Linq always rocks:
_session.Query<Show>()
.Where(s => s.CountryOrigin.Name = countryName)
.OrderBy(s => EnglishName)
.ToList();
(.Query is for NH 3.x; for 2.x use .Linq)