C# MongoDB joins and lookup - c#

I'm new to MongoDB trying to wrap my head around lookup aggregate and joins using these simple classes
public class Car
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Color { get; set; }
// List of supported engine IDs
public List<string> EngineIds { get; set; }
public List<string> TireIds { get; set; }
// This is probably not the best way to do this but it's here while testing
[BsonIgnore]
public List<Engine> Engines { get; set; }
[BsonIgnore]
public List<Tire> Tires { get; set; }
}
public class Engine
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public double Volume { get; set; }
}
public class Tire
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public int Size { get; set; }
}
Running the following Linq query is my issue, while it does find and return the correct Car item from the DB it does not include the related Engines or Tires (I tried both).
//This returns the correct car but without filling the Engines prop
//How to also get related tires ? multiple queries ?
var car123 = DataService.CarCollection.Aggregate()
.Match(c => c.Id == "123")
.Lookup<Car, Engine, Car>(
DataService.EngineCollection,
c => c.EngineIds,
e => e.Id,
car => car.Engines
).FirstOrDefault();
Another issue is how to include both Engines and Tires relating to my main object Car using a single query?

Related

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.

LINQ Projection and loading child objects

Having an issue with projection and getting child objects to load. The following is simplified code to represent the logic I'm trying to implement, not the actual code.
public class TicketItem
{
public int TicketItemId { get; set; }
public string TicketReason { get; set; }
public Station Station { get; set; }
public TicketOwner TicketOwner { get; set; }
}
public class Station
{
public int StationId { get; set; }
public string Name { get; set; }
}
public class TicketOwner
{
public int TicketOwnerId { get; set; }
public Employee Employee { get; set; }
public Organization Organization { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Organization
{
public int OrganizationId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CommonReasons
{
public int CommonReasonId { get; set; }
public string Reason { get; set; }
}
public TicketItem GetById(int id)
{
var query = from i in _dataContext.TicketItems
.Include("Station")
.Include("TicketOwner.Employee")
.Include("TicketOwner.Organization")
join r in _dataContext.CommonReasons on i.TicketReason equals r.CommonReasonId.ToString() into r1
from r2 in r1.DefaultIfEmpty()
where i.TicketItemId == id
select new TicketItem {
TicketItemId = i.TicketItemId,
TicketReason = r2.Reason == null ? i.Reason : r2.Reason,
Station = i.Station,
TicketOwner = i.TicketOwner
};
return query
.AsNoTracking()
.FirstOrDefault();
}
Most the code is self-explanatory. The part that is indirectly causing the trouble would be the relationship between TicketItem.TicketReason property (a string) and the CommonReasons entity. From the user interface side, the end-user has an input field of "Reason", and they can select from "common" reasons or input an adhoc reason. They original developer chose to have the TicketReason property contain either the key ID from the CommonReasons table (if the user selected from drop-down) or the adhoc reason typed in.
So, to handle this logic in the linq query, the only way I have found is to do a left join between TicketItem.TicketReason and CommonReasons.CommonReasonId, then use projection to modify the TicketReason column returning either the common reason text or adhoc text. If there is a different way to do this that would get me around the trouble I'm having with projection/include, I'm all ears.
For the "reason" logic, this query works, returning the proper text. The trouble is that none of the "grand-child" objects are returning, i.e. TicketItem.TicketOwner.Employee, TicketItem.TicketOwner.Organization. How do I get those objects to return also?
Changing the structure of the tables would be an absolute last resort, just based on the amount of code that would have to change. There are other spots in the code that are using the above logic but don't need the child objects.
Any help would be appreciated. Hope I've explained enough.

EntityFramework: How to load List of objects from database?

I have two entities:
public class Booking
{
[Key]
public int Id { get; set; }
public int RoomId { get; set; }
[ForeignKey("RoomId")]
public Room Room { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DocumentNumber { get; set; }
public string ContactPhone { get; set; }
}
public class Room
{
[Key]
public int RoomId { get; set; }
public int Number { get; set; }
public int Size { get; set; }
public bool HasBalcony { get; set; }
public int Beds_1 { get; set; }
public int Beds_2 { get; set; }
public double DayPrice { get; set; }
public List<Booking> Bookings { get; set; }
...
public int BookingsCount()
{
return Bookings.Count;
}
public bool IsFree(DateTime dateTime)
{
MessageBox.Show(BookingsCount().ToString());
return true;
}
}
and DbContext:
public class HotelContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Room> Rooms { get; set; }
public DbSet<Booking> Bookings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Booking>()
.HasRequired(b => b.Room)
.WithMany(r => r.Bookings)
.HasForeignKey(b => b.RoomId);
}
}
When MessageBox.Show is called I'm getting exception: An unhandled exception of type 'System.NullReferenceException' occurred in Hotel.exe
When I'm trying to access Room::Bookings, the list is always null. There is one row in Bookings table and multiple rows in Rooms table.
How can I load all of Bookings into Room object?
Depends where you are in the learning curve, however some things stand out
Firstly
You either want to create a relationship via FluentApi or Annotations, not both
Ie. you have this on your Room entity
[ForeignKey("RoomId")]
And this in fluent
modelBuilder.Entity<Booking>()
.HasRequired(b => b.Room)
.WithMany(r => r.Bookings)
.HasForeignKey(b => b.RoomId);
You need to pick one or the other, otherwise you may end-up with multiple Ids in your Booking i.e RoomId and Room_Id
Secondly
If you want to be able to Lazy Load bookings you need to make Bookings collection Virtual
public virtual List<Booking> Bookings { get; set; }
Lastly
To access your data (presuming your connection string is correct)
using(var db = new HoteContext())
{
var rooms = db.Rooms.Include(x => x.Bookings).ToList();
}
Note : Although EF Lazy loads relationships, you might want to make sure you have included the Room->Booking relationship
Consider the following code.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Departments.Add(new Department()
{
Name = "Some Department1",
Employees=new List<Employee>()
{
new Employee() { Name = "John Doe" }
}
});
dbContext.SaveChanges();
var department = dbContext.Departments.FirstOrDefault(d => d.Name == "Some Department1");
if (department.Employees != null)
{
foreach (var item in department.Employees)
{
Console.WriteLine(item.Name);
}
}
}
}
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
}
}
If you have the code in above way, the control will not go into if condition, because department.Employees is null. Now, change the Department entity as follows.
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Employee> Employees { get; set; }
}
And now you should be able to see control go into if condition and outputs the employees name.
That is called Lazy Loading.
If you want to eagerly load, you don't have to put virtual to the property. You can Include the properties as follows.
var department = dbContext.Departments.Include(d => d.Employees).FirstOrDefault(d => d.Name == "Some Department1");
Now you can see the employees names are getting outputted.
You will absolutely run into performance trouble with your design here.
The temptation with EF is to completely map your object model to the DB and have EF do all the magic for you behind the scenes. But you need to think about it in terms of only getting specifically what you need from the db at any point in time. Otherwise you will get all kinds of cartesian product issues. I highly suggest you get yourself a copy of Hibernating Rhino's EF Profiler or similar so you can analyze your code statically and at runtime for EF performance issues (and see what SQL it is generating). For this what you want is a purpose built call to the DB to get the count. Otherwise what will happen is you will pull the entire table of Bookings and then have C# give you the count. That only makes sense if you want to do something with the whole list. Two options would be:
1) Create a VIEW against the Bookings table and map that to EF. The view would look something like SELECT ROOMS.ROOMID, COUNT(*) - you map this view to your model and voila now you have a list of counts by room (id) and you can use them individually or sum it up to get your total count for all rooms. If you have 1,000 bookings in 10 rooms, you are getting back only 10 rows from the DB. Whereas with your design, you are pulling back all 1,000 bookings with all their fields and then filtering down in C#. Bad juju.
2) The architecturally and conceptually simpler approach is going to be to do a direct query as such (obviously this returns only a single int from the db):
public int BookingsCount()
{
int count = 0;
try
{
using (var context = new HotelContext())
{
var sql ="SELECT COUNT(*) FROM Bookings WHERE ROOMID=" + this.RoomId;
count = context.Database.SqlQuery<int>(sql).First();
}
} catch (Exception ex)
{
// Log your error, count will be 0 by default
}
return count;
}
A simple solution would be making the Bookings property virtual.
public class Room
{
[Key]
public int RoomId { get; set; }
public int Number { get; set; }
public int Size { get; set; }
public bool HasBalcony { get; set; }
public int Beds_1 { get; set; }
public int Beds_2 { get; set; }
public double DayPrice { get; set; }
public virtual List<Booking> Bookings { get; set; }
}
More information on Entity Framework Loading Related Entities,
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

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.

Project data with children and grandchildren entities

I'm trying to get data in a suitable format for an api
What I would like is
Place
--Rating1
---RatingImage1.1
---RatingImage1.2
---UserName
---UserId
--Rating2
---RatingImage2.1
---RatingImage2.2
---UserName
---UserId
In a nutshell im trying to fetch a place, with its ratings(and rating images), with the names of the users who did the rating given the googlePlaceId
Tried this but it goes and does some circular fetching where once it fetches the user it then fetches the user rating and the response becomes massive
context.Places
.Include(x => x.Ratings.Select(y => y.User))
.Include(x => x.Ratings.Select(c => c.RatingImages))
.Single(x => x.GooglePlaceId == googlePlaceId);
I think projection or linq joins must be the way, but i havent had any success yet.
here are my POCOS
Place Poco
public class Place
{
public Place()
{
Ratings = new List<Rating>();
Favourites = new List<Favourite>();
}
public int Id { get; set; }
public string Name { get; set; }
public string GooglePlaceId { get; set; }
public ICollection<Rating> Ratings { get; set; }
public ICollection<Favourite> Favourites { get; set; }
}
Rating POCO
public class Rating
{
public Rating()
{
RatingImages = new List<RatingImage>();
}
public int Id { get; set; }
public float RatingValue { get; set; }
public string RatingComment { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
public string UserId { get; set; }
public AspNetUser User { get; set; }
public ICollection<RatingImage> RatingImages { get; set; }
}
User POCO
public partial class AspNetUser
{
public string UserName { get; set; }
public string Id { get; set; }
// the rest of the fields are omitted
}
Although you've omitted the definition of AspNetUser, I'm guessing it has a navigation property back to Ratings. Is this required anywhere else in your application? It won't affect the structure of your database, and removing it would allow your projection to work exactly as you've got it here. You'd still be able to display all ratings by a single user using a separate query - you've got to optimise for your most common scenario though.

Categories