How to do nested group by in RavenDB multi map index - c#

I have two different document collections in my RavenDB database - Teams and Matches. The documents look like this:
public class Team {
public string Id { get; set; }
public string Name { get; set; }
public int LeaguePosition { get; set; }
}
public class Match {
public string Id { get; set; }
public string HomeTeamName { get; set; }
public string AwayTeamName { get; set; }
public DateTime StartTime { get; set; }
}
So basically I have teams and matches between these teams. However, for certain operations I need to get an entity which look something like the following from the database:
public class MatchWithExtraData {
public string Id { get; set; } // Id from the match document.
public string HomeTeamId { get; set; }
public string HomeTeamName { get; set; }
public int HomeTeamPosition { get; set; }
public string AwayTeamId { get; set; }
public string AwayTeamName { get; set; }
public int AwayTeamPosition { get; set; }
public DateTime? StartTime { get; set; }
}
What I want is really the match document but with extra fields for the home and away teams' ids and league positions. Basically join the match document on home and away team name with two team documents, one for the home team and one for the away team. I figured that a multi map/reduce index should do the trick so I have started with the following index:
public class MatchWithExtraDataIndex: AbstractMultiMapIndexCreationTask<MatchWithExtraData> {
public MatchWithExtraData() {
AddMap<Team>(
teams => from team in teams
select new {
Id = (string)null,
HomeTeamId = team.Id,
HomeTeamName = team.Name,
HomeTeamPosition = team.LeaguePosition,
AwayTeamId = team.Id,
AwayTeamName = team.Name,
AwayTeamPosition = team.LeaguePosition,
StartTime = (DateTime?)null
}
);
AddMap<Match>(
matches => from match in matches
select new {
Id = match.Id,
HomeTeamId = (string)null,
HomeTeamName = match.HomeTeamName,
HomeTeamPosition = 0,
AwayTeamId = (string)null,
AwayTeamName = match.AwayTeamName,
AwayTeamPosition = 0,
StartTime = match.StartTime
}
);
Reduce = results => from result in results
// NOW WHAT?
}
}
The reduce part is the one I can't figure out since there are two teams in each match. I think I need to do a nested group by, first on the HomeTeamName, and then on the AwayTeamName but I can't figure out how to do that.
Maybe this is more a LINQ problem than a RavenDB problem. But how would such a nested group by statement look? Or could it be done in another way?

You are better off using Transform Results for that, or includes.
See the docs here: http://ravendb.net/docs/client-api/querying/handling-document-relationships

Related

One to zero or one relationship Entity Framework, getting the relation of an entity

I'm trying to establish a one to zero or one relationship in EF Core, but I can't seem to go into a model and get it's related model.
public class Voucher
{
[Key]
public long id { get; set; }
public long voucherId { get; set; }
public long number { get; set; }
public string Type { get; set; }
public string description { get; set; }
public DateTime date { get; set; }
public int paymentId { get; set; }
public Invoice invoice { get; set; }
[ForeignKey("client")]
public long clientFK { get; set; }
public Client client { get; set; }
public ICollection<Post> posts { get; set; } = new List<Post>();
}
public class Invoice
{
[Key, ForeignKey("voucher")]
public long voucherFK { get; set; }
public long invoiceId { get; set; }
public string clientId { get; set; }
public DateTime dueDate { get; set; }
public decimal amountTotal { get; set; }
public string specification { get; set; }
public string invoicePdf { get; set; }
public long orderId { get; set; }
public Voucher voucher { get; set; }
public ICollection<InvoiceLine> invoiceLines { get; set; } = new List<InvoiceLine>();
}
Now I can create Vouchers without the need for an invoice, and unnable to create an invoice without a voucher. (I've also tried several other ways to map this relationship)
The problem is when I'm trying to fetch invoices that are bound to vouchers or the other way around.
Here's an example of how I tried to do it:
[HttpGet("testing1")]
public List<Voucher> getInvoiceTest(string filter)
{
long tenantId = getTenantId();
DateTime comparisonDate = compareDates(filter);
var invoices = _warehouseDb.Invoices
.Where(v => v.voucher.client.tenantFK == tenantId)
.Where(d => d.voucher.date >= comparisonDate)
.OrderByDescending(p => p.voucher.paymentId).ThenByDescending(d => d.voucher.date)
.ToList();
//return invoices;
List<Voucher> vouchers = new List<Voucher>();
for (int i = 0; i < invoices.Count - 1; i++)
{
if (invoices[i].voucher != null)
{
Console.WriteLine("i value: " + i);
Voucher voucher = new Voucher();
voucher = invoices[i].voucher;
vouchers.Add(voucher);
}
}
return vouchers;
}
I've tried without the ending forloop to see if I actually get a list of invoices, and I do. The filter part is just to get it within the correct time-span.
Once I reach the forloop, it can't seem to get any vouchers connected to any of the invoices.
Have I connected the models wrong or what's going on? This exact type of code works for other models where the relationship is one-to-many.
I've also tried mapping the models with virtual tag to see if it made any difference. I also had invoices save a FK for vouchers, but my main goal is to go through a list of vouchers and get it's invoice.
Any suggestion is appreciated, if not, thanks for reading.
I'm using Entity framework core and postgreSQL

Filtering on the Collection Navigation property

I would like to filter my 'TranslationSet' entities, based on their 'Translations' Collection Navigation Property.
E.g.
If a 'Translation' has a 'LanguageId' of 5 (Italian), then the 'TranslationSet' that contains this 'Translation' should be removed from the result.
Here are my Entity classes:
public class Language
{
public int LanguageId { get; set; }
public string NationalLanguage { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public List<Translation> Translation { get; set; } = new List<Translation>();
}
public class Translation
{
public int TranslationId { get; set; }
public string TranslatedText { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public int TranslationSetId { get; set; }
public TranslationSet TranslationSet {get; set;}
}
public class TranslationSet
{
public int TranslationSetId { get; set; }
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public IEnumerable<Translation> Translations { get; set; }
}
Here is my attempt
From the image you can see that the query fails because a Translation exists with LanguageId of 5.
I have tried many many attempts to resolve this but I can't even get close the LINQ which returns my query correctly.
Please let me know if any further clarification is needed and thanks in advance to anybody who offers help.
My rule of the thumb that nearly always work is: start by querying the entities you want. That will prevent duplicates as you see in your query result. Then add predicates to filter the entities, using navigation properties. That will be:
var sets = TranslationSets // start the query here
.Where(ts => ts.Translations.All(t => t.LanguageId != 5)); // Filter
Or if you like this better:
var sets = TranslationSets // start the query here
.Where(ts => !ts.Translations.Any(t => t.LanguageId == 5)); // Filter
EF will translate both queries as WHERE NOT EXISTS.

Is this the right way of using Dapper or am I doing it all wrong?

I am trying to get away from the Entity Framework since I have to support HANA Databases aside from SQL server Databases in our solution.
I am doing some research with dapper so I created a quick test environment with some fictitious scenario.
I have the following POCOs that resemble my Database schema (I have more but I limited to showing these for simplicity):
public class Adopter
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public State State { get; set; }
public int StateId { get; set; }
public string Zip { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public IEnumerable<Pet> Pets { get; set; }
}
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public string Abreviation { get; set; }
}
public class Pet
{
public int Id { get; set; }
public string IdTag { get; set; }
public string Name { get; set; }
public DateTime AdmitionDate { get; set; }
public Status Status { get; set; }
public int StatusId { get; set; }
public string Notes { get; set; }
public DateTime AdoptionDate { get; set; }
public bool IsAdopted { get; set; }
public int? AdopterId { get; set; }
public int Age { get; set; }
public decimal Weight { get; set; }
public string Color { get; set; }
public Breed Breed { get; set; }
public int BreedId { get; set; }
public Gender Gender { get; set; }
public int GenderId { get; set; }
public IEnumerable<PetImage> PetImages { get; set; }
}
public class Status
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Gender
{
public int Id { get; set; }
public string Name { get; set; }
}
I am using the following in a repository to return a list of all the adopters:
using (SqlConnection connection = new SqlConnection(_connectionString))
{
var adopters = connection.Query<Adopter>("SELECT a.* FROM Adopters a");
foreach (var adopter in adopters)
{
adopter.State = connection.QueryFirst<State>("Select s.* FROM States s WHERE s.Id = #Id", new { Id = adopter.StateId });
adopter.Pets = connection.Query<Pet>("Select p.* FROM Pets p WHERE p.AdopterId = #Id", new { Id = adopter.Id });
foreach (var pet in adopter.Pets)
{
pet.Status = connection.QueryFirst<Status>("Select s.* FROM Status s WHERE s.Id = #Id", new { Id = pet.StatusId });
pet.Gender = connection.QueryFirst<Gender>("Select g.* FROM Genders g WHERE g.Id = #Id", new { Id = pet.GenderId });
}
}
return adopters;
}
As you can see, I am retrieving the data for each POCO individually based on the previous one and doing the Joins manually in code.
Is this the right way of doing it or should I be doing a big query with multiple joins and mapping the result somehow thru dapper and LINQ?
A possible improvement to your actual solution is through the use of QueryMultiple extension like this:
using (SqlConnection connection = new SqlConnection(_connectionString))
{
string query = #"SELECT * FROM Adopters;
SELECT * FROM States;
SELECT * FROM Pets;
SELECT * FROM Status;
SELECT * FROM Genders;";
using (var multi = connection.QueryMultiple(query, null))
{
var adopters = multi.Read<Adopter>();
var states = multi.Read<State>();
var pets = multi.Read<Pet>();
var statuses = multi.Read<Status>();
var genders = multi.Read<Gender>();
foreach (Adopter adp in adopters)
{
adp.State = states.FirstOrDefault(x => x.Id == adp.StateID);
adp.Pets = pets.Where(x => x.IsAdopted &&
x.AdopterID.HasValue &&
x.AdopterID.Value == adp.AdopterID)
.ToList();
foreach(Pet pet in adp.Pets)
{
pet.Status = statuses.FirstOrDefault(x => x.Id == pet.StatusID);
pet.Gender = genders.FirstOrDefault(x => x.Id == pet.GenderID);
}
}
}
}
The benefit here is that you reach the database just one time and then process everything in memory.
However this could be a performance hit and a memory bottleneck if you have a really big data to retrieve, (and from a remote location). Better to look closely at this approach and try also some kind of Async processing and/or pagination if possible.
I don't like to be negative, but... don't do this! Don't even think like this. You want to dump EF, but you're walking into the trap by wanting to emulate EF. The bridge between your app and your DB is not something to be built once for all time, for every conceivable purpose. Concretely, you shouldn't really ever bring back a whole table, and certainly not to then loop on every row and emit more queries. You may feel unjustly criticised, you were just testing the tools ! If so, perhaps tell us what aspect of the tool your examining, and we'll focus in on that.
Dapper or QueryFirst greatly simplify running queries, and consuming the results, so bring back just what you need, just when you need it. Then denormalize a little, for the specific job in hand. Why are there no joins in your queries? RDBMSs are amazing, and amazingly good at doing joins. If you're joining data outside the DB, crazy is the only word, even if Linq gives you a super (sql-like) syntax for doing it. The unthinking assumption that 1 table corresponds to 1 class is the start of a lot of problems.

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.

Mongodb C# Spatial Query

I'm using the csharpdriver on a Windows 7 machine.
In summary the data structure:
public class Site
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
public List<SiteLocation> Locations = new List<SiteLocation>();
}
public class SiteLocation
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public List<string> Address { get; set; }
public GeoJsonPoint<GeoJson2DGeographicCoordinates> Location { get; set; }
}
the code to create the index:
Database.GetCollection<Site>("site")
.EnsureIndex(IndexKeys<SiteLocation>.GeoSpatialSpherical(x => x.Location));
The query to find points near:
var point = GeoJson.Point(GeoJson.Geographic(153.0, -27.5)); //long, lat
var locationClause = Query<SiteLocation>.Near(p => p.Location, point, 2000);
var query = _ctx.Sites.AsQueryable().Where(x => locationClause.Inject());
var results = query.ToList();
Unfortunately results returns a count of zero. And there is a record with the same lat,long and others very close.
Any ideas where the problem is?

Categories