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.
Related
I am trying to create a table that shows the top 4 nationalities in my database for my .NET application. I can do it using SQL following this.
This is the result in sql
However, what I need for my application is
I have tried using LINQ as well as var in the View but unable to get it. I saw this and tried following but could not quite understand
Employee Model
public class employees
{
public int eeID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string Nationality { get; set; }
}
You can do the below things
Store the Grouped Nationalities sorted as per their counts
Get the top results, use Enumerable.Take
Get the count of the other Nationalities which should not include the top records, For that, you can use Enumerable.Skip
Concat result from points 2 and 3.
Pass the final output to the view.
Sample Code. Please refer to this link for working code
// Your Logic
var topRecordsCount = 4;
var groupedResult = list
.GroupBy(x => x.Nationality)
.OrderByDescending(x => x.Count());
var topRecords = groupedResult
.Take(topRecordsCount)
.Select(x => new FinalResult {Nationality = x.Key, Total = x.Count()}).ToList();
var othersCount = groupedResult.Skip(topRecordsCount).Select(x => x.Count()).Sum();
var othersRecord = new FinalResult { Nationality = "Others", Total = othersCount};
topRecords.Add(othersRecord);
foreach(var top in topRecords)
Console.WriteLine(top.Nationality + " - " + top.Total);
// model to store the final output
public class FinalResult {
public string Nationality { get; set; }
public int Total { get; set; }
}
Example: Let’s assume the following collections. ThrashMetalDocumentsCollection and SpeedMetalDocumentsCollection, both collections having the same HeavyMetalRecordDocument structure as shown below. How do I query and return ALL of the records in both collections and sort them by releaseDate (oldest first) and rating (high to low)? Thanks! \m/ \m/
static async Task getAllRecords()
{
var builder = Builders<HeavyMetalRecordDocument>.Filter;
//var filter;
using (var cursor = await ThrashMetalDocumentsCollection.Find()
.Sort(Builders<HeavyMetalRecordDocument>.Sort.Ascending(x => x.rating))
.ToCursorAsync())
{
while (await cursor.MoveNextAsync())
{
foreach (var doc in cursor.Current)
{
//Do Something…
}
}
}
}
public class HeavyMetalRecordDocument
{
public ObjectId Id { get; set; }
public String artist { get; set; }
public String title { get; set; }
public DateTime releaseDate { get; set; }
public int rating { get; set; } // 1-10
}
MongoDB is not a relational database, and as such, it cannot perform joins (which is what you are trying to accomplish). You probably want to think more about the structure of your database. Without knowing any more about what you are trying to do, I would suggest putting all types of albums into a single collection - putting in an additional field indicating the genre of the album.
public class PersonBrief
{
public int Id { get; set; }
public string Picture { get; set; }
public PersonBrief(Person person)
{
Id = person.Id;
Picture = person.Picture;
}
}
public class Person : PersonBrief
{
public string FullName { get; set; }
}
var results = session.Query<Person>()
.Select(x => new PersonBrief(x))
.ToList();
Assert.IsNull(results[0] as Person); // Fails
Is this a bug? If not, what would be the correct way to select only the fields i'm interested in?
It would work if you move the .ToList before the .Select, but that would be doing the work on the client.
If you want to do it on the server, you need to use As in your query, and you need a static index that does a TransformResults. See these docs.
I'm trying to use the TransformResults feature, and I can't get it to work. I'm not totally sure I understand this feature, perhaps there is another way to solve this problem. What I want is just the Id from the Order and the email addesses from the Customer and the Entrepreneur. I am happy for all tips that can take me in the right direction. Here is my code.
Document
public class OrderDocument
public string Id {get; set }
public EntrepreneurInfo EntrepreneurInfo { get; set; }
public CustomerInfo CustomerInfo { get; set; }
public OrderStatus CurrentOrderStatus { get; set; }
}
Info classes
public class EntrepreneurInfo
{
public string EntrepreneurDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public class CustomerInfo
{
public string CustomerDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
The info classes are just subsets of a Customer and Entrepreneur documents respectively.
The Customer and Entrepreneur documents inherits from a base class ( AbstractOrganizationDocument) that has the EmailAddress property.
My Index
public class OrdersApprovedBroadcastingData :
AbstractIndexCreationTask<OrderDocument, OrdersApprovedBroadcastingData.ReduceResult>
{
public OrdersApprovedBroadcastingData()
{
this.Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
Id = d.Id,
CustomerId = d.CustomerInfo.CustomerDocumentId,
EntrepreneurId = d.EntrepreneurInfo.EntrepreneurDocumentId
};
this.TransformResults = (db, orders) => from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurId)
select
new
{
o.Id,
o.CustomerId,
CustomerEmail = customer.EmailAddress,
o.EntrepreneurId,
EntrepreneurEmail = entrepreneur.EmailAddress
};
}
public class ReduceResult
{
public string Id { get; set; }
public string CustomerId { get; set; }
public string CustomerEmail { get; set; }
public string EntrepreneurId { get; set; }
public string EntrepreneurEmail { get; set; }
}
}
If I look at the result of this Index in Raven Studio I get null values for all fields except the Id. And finally here is my query.
Query
var items =
this.documentSession.Query<OrdersApprovedBroadcastingData.ReduceResult, OrdersApprovedBroadcastingData>()
.Select(x => new OrdersToBroadcastListItem
{
Id = x.Id,
CustomerEmailAddress = x.CustomerEmail,
EntrepreneurEmailAddress = x.EntrepreneurEmail
}).ToList();
Change your index to:
public class OrdersApprovedBroadcastingData : AbstractIndexCreationTask<OrderDocument>
{
public OrdersApprovedBroadcastingData()
{
Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
};
TransformResults = (db, orders) =>
from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerInfo.CustomerDocumentId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurInfo.EntrepreneurDocumentId)
select new
{
o.Id,
CustomerEmailAddress = customer.EmailAddress,
EntrepreneurEmailAddress = entrepreneur.EmailAddress
};
}
}
Your result class can simply be the final form of the projection, you don't need the intermediate step:
public class Result
{
public string Id { get; set; }
public string CustomerEmailAddress { get; set; }
public string EntrepreneurEmailAddress { get; set; }
}
You don't have to nest this class in the index if you don't want to. It doesn't matter either way. You can query either with:
var items = session.Query<Result, OrdersApprovedBroadcastingData>();
Or with
var items = session.Query<OrderDocument, OrdersApprovedBroadcastingData>().As<Result>();
Though, with the first way, the convention tends to be to nest the result class, so really it would be
var items = session.Query<OrderDocument.Result, OrdersApprovedBroadcastingData>();
Note in the index map, I am not including any properties at all. None are required for what you asked. However, if you want to add a Where or OrderBy clause to your query, any fields you might want to filter or sort on should be put in there.
One last thing - the convention you're using of OrderDocument, CustomerDocument, EntrepreneurDocument, is a bit strange. The usual convention is just Order, Customer, Entrepreneur. Think of your documents as the persisted form of the entities themselves. The convention you are using will work, it's just not the one usually used.
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)};