ravendb index, query on child collection - c#

I want to get a summary of all products, as only the latest OrderHistory is of interest where I want to use this. I have thousands of products with hundreds of OrderHistory each, but now I only want the product id and the latest OrderHistory for each product.
public class ProductSummary
{
public int ProductId { get; set; }
public OrderHistory LastOrderHistory { get; set; }
}
The OrderHistory is stored inside the Product document like this:
public class Product
{
public int Id { get; set; }
public int MarketGroupId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<OrderHistory> OrderHistory { get; set; }
}
And this is what OrderHistory looks like:
public class OrderHistory
{
public long OrderCount { get; set; }
public long Volume { get; set; }
public DateTime date { get; set; }
public double AvgPrice { get; set; }
}
Now I've tried a few approaches on the index and query to get this running, this is my latest try, but it returns no results.
public class LatestProductOrderHistory : AbstractIndexCreationTask<Product, ProductSummary>
{
public LatestProductOrderHistory()
{
Map = products => from p in products
from oh in p.OrderHistory
select new
{
ProductId = p.Id,
LastOrderHIstory = p.OrderHistory.OrderByDescending(o => o.date).Last()
};
StoreAllFields(FieldStorage.Yes);
}
}
And finally my query:
var results = session
.Query<ProductSummary, LatestProductOrderHistory>()
.ProjectFromIndexFieldsInto<ProductSummary>()
.Take(1024)
.Skip(start)
.ToList();
This combination gives me no results, I have never made indexes in ravendb before, so I'm sorry if this is a dumb question.
EDIT: Well, I'm not sure what I changed, but now I'm getting "Could not read value for property: Id"
EDIT2: The strange issue in previous edit was solved by restarting vs and ravendb, so current result is no result

As Ayende commented, you must add the Reduce function to your index as:
Reduce = results => from result in results
group result by result.Id into g
select new
{
Id = g.Key,
LastOrderHistory = g.SelectMany(x=> x.LastOrderHistory)
.OrderByDescending(o => o.Date).FirstOrDefault()
};
Just selecting wanted fields in Map function does not make your index Map/Reduce.
Then query your index as:
session.Query<ProductSummary, LatestProductOrderHistory>()

Related

How can I do a self join in ORMLite

I'm trying to get all time entries for a specific foreman based on his supervisor's supervisor. However, I seem to be having trouble writing a self join query in ORMLite. See my data structure and code below.
public class User
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[References(typeof(User))]
public int SupervisorId { get; set; }
}
public class TimeSheet
{
[AutoIncrement]
public int Id { get; set; }
[References(typeof(User))]
public int ForemanId { get; set; }
}
var query = db.From<TimeSheet>()
.Join<User>()
.Join<User, User>(); // not sure how to write this one.
// .Where(super => super.SupervisorId = 2)
I've created a sample gist to try and better show what I've attempted.
var query = db.From<TimeSheet>()
.Join<User>()
.Join<User, User>((p, q) => p.Id == q.SupervisorId, db.JoinAlias("u2"));

Error iterating the query results

I have a context called companyContext. There are Three tables Reports,Logs and Employees.I am given a case id and I need to get a list of all the employees who belong to a certain case (and a log if there is one, but i dont need to worry about that yet). So I made a query get all the employees where EmployeeID is equal to Employee.ID, where the Reports CaseID is eaual to case.id. However, its not reutning a list, its returning a Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable}]
Am I making the query correctly ? Thanks guys.
var employees = await context.Reports.Include(s => s.Employee)
.ThenInclude(e => e.ID)
.AsNoTracking()
.Where(r => r.CaseID == Case.Id);
Models
public class Log
{
public int ID { get; set; }
public string Input { get; set; }
public string Tag { get; set; }
public DateTime LogDate { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Report
{
public int ID { get; set; }
public string CaseID { get; set; }
public int EmployeeD { get; set; }
public int? LogID { get; set; }
public Employee Employee { get; set; }
public Log Log { get; set; }
}
SingleOrDefaultAsync
throws an exception if there is more than one element in the sequence.
So you should not be using SingleOrDefaultAsync if you expect multiple records.
If I'm interpreting your question properly and you want employees themselves, you could start with them and then narrow via the Employee class's .Reports navigation property.
var employees = await context.Employees.AsNoTracking()
.Where(e => e.Reports.Any(r => r.CaseID == case.Id))
.ToListAsync();
So entity is actually like using a real database. I had to do a where and select clause, but also a toList to convert it into something readable.
var employees = await context.Reports.Where(r => r.CaseID == Case.Id);
.Select(r => r.Employee)
.ToList();

LINQ Group By Count for Many to Many

I have 2 entities, with a 1 to many relationship, and I'm going to switch it to many to many but I need help with grouping and counts.
SearchString -> many JobResults
A SearchSting is used to find job results and job results are stored as a collection property of SearchString:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual SearchString SearchString { get; set; }
}
I get the top 5 JobFunctions of all job results as follows:
var top5jobfunctions = JobSearchResults.Where(a => (a.SearchString != null)).
GroupBy(s => new { s.SearchString.JobFunction.JobFunctionId, s.SearchString.JobFunction.JobFunctionName }).
Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() }).
OrderByDescending(x => x.count).
Take(5).ToList();
I'm going to switch it to many to many as such:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual ICollection<SearchString> SearchStrings { get; set; }
}
How do I get my top 5 jobfunctions counts once I switch it to many to many?
Also, is the structure I chose the right approach? For example I wonder if having jobresults a child collection of SearchString was maybe not the best way to go and that perhaps I should just have SearchStrings be a collection property of JobResult.
For the modified model with many many relationship, consider the following modification to your original query:
var top5jobfunctions =
JobSearchResults.SelectMany(j => j.SearchString.Select(s => new {j,s}))
.Where(j => (j.s != null))
.GroupBy(j => new { j.s.JobFunction.JobFunctionId, j.s.JobFunction.JobFunctionName })
.Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() })
.OrderByDescending(x => x.count)
.Take(5).ToList();
Explanation:
Now since JobSearchResult contains ICollection<SearchString>, it needs flattening to execute a similar query as earlier
SelectMany flattens the data and fills the results as an anonymous type, which contains a record for each SearchString
Henceforth similar logic as you have designed is followed
Model Correctness
I would not prefer, this kind of relationship, as it makes overall querying and data insertion unnecessarily complex
In my understanding a 1 to Many relationship would do as good a job in fetching all the relevant information, in this case you may consider just having ICollection<JobSearchResult> aggregated inside SearchString or vice versa relationship based on suitability, I am not sure what kind of use case does a circular many many relationship model solve.

Get data from nested table using entity framework

First of all this is my first question in the forum so please excuse me for any writing mistake.
I have 4 tables
attaching the table diagram
What I want is to get list of attraction name joining 'tblattraction' with 'tblattractionmaster' and count of the exact attraction for each place from 'tblattractions' using 'locationid' , I am using entity framework but don't know how to do that,
Disclaimer:
Each location can consist Multiple Places
Each Place can consist Multiple Attractions
What I have tried
return context.tblLocationMasters.Select(t => new details()
{
locationid = t.LocationId,
locationname = t.LocationName,
attractions =t.tblPlaces.SelectMany(a => a.tblAttractions).Select(b => new attractions(){
AttractionName=b.tblAttractionMaster.attractionname//(Not working),
TotalAttractions=0//???
}).ToList()
}).ToList();
I recreated your model (slightly different) using Code First. I came up with the following structure:
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public ICollection<Place> Places { get; set; }
}
public class Place
{
public int PlaceId { get; set; }
public string PlaceName { get; set; }
public int LocationId { get; set; }
public Location Location { get; set; }
public ICollection<AttractionPlace> Attractions { get; set; }
}
public class Attraction
{
public int AttractionId { get; set; }
public string AttractionName { get; set; }
}
public class AttractionPlace
{
public int AttractionPlaceId { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
public int AttractionId { get; set; }
public Attraction Attraction { get; set; }
}
Then, I could get the results in the way you needed with the following query:
var query = (from loc in db.Locations
join pla in db.Places.Include(x => x.Attractions) on loc.LocationId equals pla.LocationId
let count = pla.Attractions.Count()
select new
{
loc.LocationId,
loc.LocationName,
Attractions = pla.Attractions.Select(z => new
{
pla.PlaceName,
z.AttractionId,
z.Attraction.AttractionName
}),
AttractionsByPlaceCount = count
});
The query above returns data in this format
Just a side note though: I didn't went further to see the performance of this query. The SQL generated by Linq wasn't that bad, but you should consider analyzing it before actually using it in production.

Trouble populating selectlist in controller code - C# MVC

I have a fairly straight forward requirement - to populate a viewmodel, which has a SelectList as one of its properties - NewOccs is defined on the model as:
public class RatesList
{
[Key]
public long TypeID { get; set; }
public string TypeName { get; set; }
public int TypeCount { get; set; }
public IEnumerable<SelectListItem> NewOccs { get; set; }
}
My controller code to populate it is:
var rooms = dbt.Rooms.Where(r => r.hotel_id == AccID)
.GroupBy(p => p.RoomTypes).Select(g => new RatesList
{
TypeName = g.Key.type_name,
TypeCount = g.Count(),
NewOccs = dbt.Rates.Where(rt => rt.type_id == g.Key.type_id).GroupBy(rt => rt.occ).AsEnumerable()
.Select(proj => new SelectListItem
{
Text = proj.Key,
Value =proj.Key
})
}).ToList();
The Rates table it should be getting its information from is:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
}
How to I access any of the other fields in my Rates table - when I'm populating the SelectList? For example, in VSExpressIDE intellisense only allows me to type proj.Key - the other properties are not there. I want occ to be the key/value and I would like the text to be a concatenation of occ and rate - ie:
Text = proj.occ + ' ' + rate.ToString()
...but rate and occ cannot be found in intellisense.
Thank you, Mark
If you step through your debugger, you'll see that GroupBy() provides a GroupedEnumerable, which contains Keys. The keys are Lookup<string, Rates>, because you used GroupBy on a string.
If you changed your Select to a SelectMany, you'd see all your Rates. But that would defeat the purpose of the GroupBy. I'm not totally sure what you want in the end, but here is a good guide to GroupBy
Like this:
public class Client
{
public int SelectedSexId { get; set; }
public IList<Sex> SexList { get; set; }
public IList<SelectListItem> SexListSelectListItems
{
get
{
SexList=SexList??new List<Sex>();
var list = (from item in SexList
select new SelectListItem()
{
Text = item.Name,
Value = item.Id.ToString(CultureInfo.InvariantCulture)
}).ToList();
return list;
}
set { }
}
}

Categories