I wanted to retrieve the top 5 merchants with the latest order and the grand total amount. However, I can't seem to get the Grand total amount. I think the issue lies within LastOrderGrandTotal= x.FirstOrDefault().GrandTotal. The code below syntax is in LINQ method syntax
var list = _ApplicationDbContext.OrderTransaction
.Where(x => x.Status != 0)
.GroupBy(x => x.MerchantUserId)
.Select(x => new DashboardActiveMerchantStore { MerchantUserId = x.Key, CreatedDate = x.Max(c => c.CreatedDate), LastOrderGrandTotal= x.FirstOrDefault().GrandTotal })
.OrderByDescending(x => x.CreatedDate)
.Take(5)
.ToList();
For further clarification the SQL syntax for the query above is
select TOP(5) MerchantUserId,MAX(CreatedDate),GrandTotal from OrderTransaction
group by MerchantUserId
order by CreatedDate desc
I have a class to store the data retrieve from the LINQ syntax
public class DashboardActiveMerchantStore
{
public string MerchantName { get; set; }
public double LastOrderGrandTotal { get; set; }
public Guid MerchantUserId { get; set; }
public DateTime CreatedDate { get; set; }
}
As you are using entity framework, a totally different, more natural approach is possible. You can use the virtual ICollection<...>.
I wanted to retrieve the top 5 merchants with the latest order and the grand total amount.
The classes in entity framework
It seems there is a one-to-many relation between Merchants and OrderTransactions: every Merchant has zero or more OrderTransactions, and every OrderTransaction is a transaction of exactly when Merchant, namely the Merchant that the foreign key MechantId refers to.
If you've followed the entity framework conventions, you will have classes similar to:
public class Merchant
{
public Guid Id {get; set;} // primary key
public string Name {get; set;}
...
// Every Merchant has zero or more TransactionOrders (one-to-many)
public virtual ICollection<TransactionOrder> TransactionOrders {get; set;}
}
public class TransactionOrder
{
public Guid Id {get; set;} // primary key
public DateTime CreatedDate {get; set;}
public int Status {get; set;}
public double GrandTotal {get; set;}
...
// every TransactionOrder is an order of a Merchant, using foreign key
public Guid MerchantId {get; set;}
public virtual Merchant Merchant {get; set;}
}
And of course the DbContext:
public class OrderDbContext : DbContext
{
public DbSet<Merchant> Merchants {get; set;}
public DbSet<TransactionOrder> TransactionOrders {get; set;}
}
This is all that entity framework needs to detect the tables, the columns of the tables and the one-to-many relation between the table. Only if you deviate from the conventions, like you want to use different property names, or different table names, you need attributes or fluent API.
In entity framework the columns of a table are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Get top 5 Merchants
If you want some information about "merchants that ... with some information about some of their OrderTransactions" you start at dbContext.Merchants;
If you want some information about "OrderTransactions that ..., each with some information about their Merchant", you start at dbContext.OrderTransactions.
I want to query the top 5 merchants with the latest order and the grand total amount
So you want Merchants. From these Merchants you want at least their Id, and their Name, and some information about their OrderTransactions.
Not all OrderTransactions, only information about their last OrderTransaction with a non-zero Status.
From this last OrderTransaction you want the CreatedDate and the GrandTotal.
Now that you've got Merchants with their last non-zero Status Order, you don't want all these Merchants, you only want the five Merchants with the newest CreatedDate.
I hope the above is your requirement. It is not what your SQL said, but your SQL didn't fetch Merchants, it fetched groups of TransactionOrders.
var result = dbContext.Merchants
.Select(merchant => new
{
Id = merchant.Id,
Name = merchant.Name,
// from Every Merchant, get the Date and Total of their last
// non-zero status Order
// or null if there is no such order at all
LastOrder = merchant.OrderTransactions
.Where(orderTransaction => orderTransaction.Status != 0)
.OrderByDescending(orderTransaction => oderTransaction.CreatedDate)
.Select(orderTransaction => new
{
CreatedDate = orderTransaction.CreatedDate,
GrandTotal = orderTransaction.GrandTotal,
})
.FirstOrDefault(),
})
Entity Framework knows your one-to-many relation. Because you use the virtual ICollection<...> it will automatically create the (Group-)Join for you.
Now you don't want all Merchants, you don't want Merchants without LastOrder. They didn't have any Order with a non-zero Status. From the remaining Merchants you only want the five Merchants with the newest LastOrder.CreatedDate.
So remove all Merchants that have no LastOrders, order by descending LastOrder.CreatedDate and take the top 5.
Continuing the LINQ:
.Where(merchant => merchant.LastOrder != null) // at leas one Order with non-zero Status
.OrderbyDescending(merchant => merchant.LastOrder.CreatedDate)
.Take(5).
You will have "Merchants (Id and Name) with their LastOrder (CreatedData and GrandTotal)", if you want, add an extra Select to convert this into five DashboardActiveMerchantStores
I would rewrite the query like this. You may not even need FirstOrDefault because with GroupBy I believe there is always at least a record.
var result = data
.Where(x => x.Status != 0)
.GroupBy(x => x.MerchantUserId)
.Select(q => new
{
q.Key,
Orders = q.OrderByDescending(q => q.CreatedDate),
})
.Select(x => new DashboardActiveMerchantStore {
MerchantUserId = x.Key,
CreatedDate = x.Orders.FirstOrDefault().CreatedDate,
LastOrderGrandTotal = x.Orders.FirstOrDefault().GrandTotal,
})
.Take(5)
.ToList();
var lastFiveProducts = products.OrderByDescending(p => p.LastOrderGrandTotal).Take(5)
Related
I have a table which named movies and it has genres column. In genres column, I store the movie's genres id like [0,5,10] type. Those are foreign keys but stored in different way. Is there any way to use those foreign keys with EF.
I want to use it like List<genres> x = movie.genres.
Thanks.
"I thought that, store the genres id like this way can be more efficient."
Nope. Keep it simple and let the tools (EF and Database) do what they do. The efficiency is already built there, no need to try reinventing something. Start with the simplest, most standard thing, then look at alternatives to address concerns as those concerns are proven to be valid.
As far as EF is concerned, your Movies can reference a collection of Genres, and your Genre can optionally reference a collection of Movies. With the inclusion of a MovieGenre table containing just a composite key consisting of a MovieId and GenreId, EF can map this relationship automatically without needing to define a MovieGenres entity.
So for entities like:
[Table("Movies")]
public class Movie
{
[Key]
public int MovieId { get; set; }
public DateTime ReleaseDate { get; set; }
public string Name { get; set; }
public virtual ICollection<Genre> Genres { get; private set; } = new List<Genre>();
}
[Table("Genres")]
public class Genre
{
[Key]
public int GenreId { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies { get; private set; } = new List<Movie>();
}
Then in the OnModelCreating (or using entity type configuration):
modelBuilder.Entity<Movie>()
.HasMany(x => x.Genres)
.WithMany(x => x.Movies)
.Map(x => x.ToTable("MovieGenres").MapLeftKey("MovieId").MapRightKey("GenreId"));
When you go to send Movies to a UI or out via an API for instance and you can flatten genre information as you see fit. For instance to display movies released in a date range with their genres:
var movies = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.Select(x => new MovieViewModel
{
MovieId = x.MovieId,
Name = x.Name,
Genres = x.Genres.Select(g => new GenreViewModel {GenreId = g.GenreId, Name = g.Name).ToList()
}).ToList();
If you're expecting a large list of movies and the UI might display the genre name, but you don't want to transmit copies of the genre for each and every movie:
// get genres for the applicable movies
var genres = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.SelectMany(x => x.Genres)
.Distinct(new GenreEqualityComparer())
.Select(x => new GenreViewModel{ GenreId = x.GenreId, Name = x.Name})
.ToList();
// Get the movies with their Genre IDs.
var movies = context.Movies
.Where(x => x.ReleaseDate >= startDate && x.ReleaseDate <= endDate)
.Select(x => new MovieViewModel
{
MovieId = x.MovieId,
Name = x.Name,
GenreIds = x.Genres.Select(g => g.GenreId).ToList()
}).ToList();
From here, the UI/Consumer will know what they need about the Genres, and can associate the movies to their Genre by ID without the excess payload of repeated genre information for each movie record. Again, this may not be necessary, but in cases where you might be selecting a lot of references to a relative few and/or large pieces of data you have the option to do something like above.
I have two classes with a many-to-many relationship in a ASP.NET EF application. I'm trying to find all Listings that have any Categories which is posted from a view. The categories are checkboxes on the view form.
These are the classes with navigation properties simplified for example:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller but it doesn't work and I don't really know what else to try:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? The subquery in the SQL will change each search and can be any id or IDs.
You forgot to tell us what objects are in your collection items. I think they are Listings. Your case doesn't work, because itemsTemp is a collection of Categories, and every item1 is a Category, which of course can't be cast to a Listing.
Advice: to debug casting problems, replace the word var
with the type you actually expect. The compiler will warn you about
incorrect types. Also use proper identifiers in your lambda expressions.
This makes them easier to read
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later.
If I look at your SQL it seems that you want the following:
I have a DbContext with (at least) Listings and Categories.
I want all Listings with their Categories that have category Id 1 or 3
It's good to see that you followed the entity framework code-first conventions, however you forgot to declare your collections virtual:
In entity framework the columns in a table are represented by
non-virtual properties. The virtual properties represent the relations
between the table.
With a slight change your many-to-many relation can be detected automatically by entity framework. Note the virtual before the ICollection
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext. Entity framework detects that you want to design a many-to-many and creates the junction table for you.
But how can I perform my joins without access to the junction table?
Answer: Don't do joins, use the ICollections!
Entity Framework knows which inner joins are needed and will do the joins for you.
Back to your SQL code:
Give me all (or some) properties of all Listings that have at least one Category with Id equal to 1 or 3
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. Hence it is wise to only transfer the properties you actually plan to use. For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the one part in the one-to-many.
Back to your code
It seems to me, that your items are Listings. In that case your code wants all Listings that have at least one enabled Category
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
Do you have a relation between Listing/Category and CategoryListings?
Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId))
.Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing or Category, Include() extension will help.
Using ASP Core 2 with EF Core and SQL Server. I have, what I think is a straightforward task of retrieving a list of manufacturers (or individual manufacturer) for a given distributor.
The Users table provides the authenticated user and each is associated with one distributor (and represented in the model as _user). So, when the action GetManufacturers() is called on the ManufacturersController, it should return all manufacturers for the given distributor. Likewise GetManufacturers(int id) should return a single manufacturer iff it is associated with the authenticated distributor.
To do this I'm trying various formulations like:
await _context.Manufacturers
.Include(a => a.Addresses)
.Include(m => m.DistributorManufacturers)
.Where (a => a.AddressesNavigation.State = "CA")
.Where (m => m.Id == id) // Manufacturers Id
.Where (d => d.DistributorManufacturers.DistributorId == _user.DistributorId)
.AsNoTracking()
.ToListAsyc()
VS is complaining that ICollection<DistributorManufacturers> does not contain a definition for DistributorId (even though I copied/pasted it from the class). It is not conceptually different from my filter on Addresses.
I've also tried .ThenInclude to add the Distributors table but no luck.
The DistributorManufacturers table was created with Scaffold-DbContext and has the foreign keys and navigation properties defined.
So, did some work to re-create your models. The only thing I changed was I added the userId in the Distributor table instead of the opposite. This will be a long answer.. so hang on
Models (omitted User and Address entities because there's nothing special with them)
public abstract class Entity
{
public int Id { get; set; }
}
public class Distributor : Entity
{
public User User { get; set; }
public int UserId { get; set; }
public Address Address { get; set; }
public int AddressId { get; set; }
public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}
public class Manufacturer : Entity
{
public Address Address { get; set; }
public int AddressId { get; set; }
public ICollection<DistributorManufacturer> DistributorManufacturers { get; set; }
}
public class DistributorManufacturer
{
public Distributor Distributor { get; set; }
public int DistributorId { get; set; }
public Manufacturer Manufacturer { get; set; }
public int ManufacturerId { get; set; }
}
Configured like this:
modelBuilder.Entity<Distributor>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UserId);
modelBuilder.Entity<Distributor>()
.HasOne(p => p.Address)
.WithMany()
.HasForeignKey(p => p.AddressId);
modelBuilder.Entity<Manufacturer>()
.HasOne(p => p.Address)
.WithMany()
.HasForeignKey(p => p.AddressId);
// many to many mapping
modelBuilder.Entity<DistributorManufacturer>()
.HasKey(bc => new { bc.DistributorId, bc.ManufacturerId });
modelBuilder.Entity<DistributorManufacturer>()
.HasOne(bc => bc.Distributor)
.WithMany(b => b.DistributorManufacturers)
.HasForeignKey(bc => bc.DistributorId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DistributorManufacturer>()
.HasOne(bc => bc.Manufacturer)
.WithMany(c => c.DistributorManufacturers)
.HasForeignKey(bc => bc.ManufacturerId)
.OnDelete(DeleteBehavior.Restrict);
Inserted this values:
select * from Users
select * from Distributors
select * from Manufacturers
select * from DistributorManufacturers
Then, in the GetManufacturers() action you wanted to return all Manufacturers for the logged in Distributor, AKA User. (This is my assumption from your question.. correct me if I'm wrong). So, down to the query:
// Simulate getting the Id of the logged in User.
var userId = 1;
var query = (from m in _context.Manufacturers
join dm in _context.DistributorManufacturers on m.Id equals dm.ManufacturerId
join dist in _context.Distributors on dm.DistributorId equals dist.Id
join adrs in _context.Addresses on m.AddressId equals adrs.Id
where dist.UserId == userId
select new
{
ManufacturerId = m.Id,
ManufacturerName = m.Name,
DistributorId = dist.Id,
DistributorName = dist.Name,
Address = adrs
}).ToList();
Resulting in this:
[
{
"manufacturerId": 1,
"manufacturerName": "Manufacturer 1",
"distributorId": 1,
"distributorName": "Distributor 1",
"address": {
"street": "Street 1",
"city": "New York",
"state": "NY",
"id": 1
}
},
{
"manufacturerId": 2,
"manufacturerName": "Manufacturer 2",
"distributorId": 1,
"distributorName": "Distributor 1",
"address": {
"street": "Street 2",
"city": "New York",
"state": "NY",
"id": 2
}
}
]
To get the GetManufacturers(int id) working, just add the Manufacturer Id to the where clause. Since it's doing a inner join on DistributorManufacturer, if there's no relationship with the logged in user it will return null.
Note: In EF Core, when you have a many-to-many relationship, you need (for now at least..) to have the joint table as an entity. You can check the discussion about this here: https://github.com/aspnet/EntityFrameworkCore/issues/1368
You can query with foreign table data like:
_context.MainTable
.Include(i=>i.ForeignTable)
.Where(w=>w.ForeignTable
.Where(wh=>wh.ForeignId==userInput).Count()>0)
.ToList();
Your query thus can be:
await _context.Manufacturers
.Include(a => a.Addresses)
.Include(m => m.DistributorManufacturers)
.Where (a => a.AddressesNavigation.State = "CA")
.Where (m => m.Id == id)
.Where (d => d.DistributorManufacturers
.Where(w=>w.DistributorId == _user.DistributorId).Count()>0)
.AsNoTracking()
.ToListAsnyc()
It seems to me you wanted to configure a many-to-many relationship between Distributors and Manufacturers: Every Distributor has zero or more Manufacturers, every Manufacturer delivers to zero or more Distributors.
If you'd configured this many-to-many relationship according to the entity framework code first many-to-many conventions, you would have something like:
class Distributor
{
public int Id {get; set;}
public string Name {get; set;}
// a distributor has exactly one Address using foreign key:
public int AddressId {get; set;}
public Address Address {get; set;}
// a Distributor has zero or more Manufacturers: (many-to-many)
public virtual ICollection<Manufacturer> Manufacturers {get; set;}
// a Distirbutor has zero or more Users: (one-to-many)
public virtual ICollection<User> Users {get; set;}
}
class Manufacturer
{
public int Id {get; set;}
public string Name {get; set;}
// a Manufacturer has exactly one Address using foreign key:
public int AddressId {get; set;}
public Address Address {get; set;}
// a Manufacturer has zero or more Distributors (many-to-many)
public virtual ICollection<Distributor> Distributors {get; set;}
}
There is also a User: every User belongs to exactly one Distributor
class User
{
public int Id {get; set;}
// a user belongs to exactly one Distributor, using foreign key:
public int DistributorId {get; set;}
public virtual Distributor Distributor {get; set;}
...
}
Finally the DbContext
class MyDbContext : DbContext
{
public DbSet<Distributor> Distributors {get; set;}
public DbSet<Manufacturer> Manufacturers {get; set;}
public DbSet<User> Users {get; set;}
public DbSet<Address> Addresses {get; set;}
}
The above is all that entity framework needs to know to understand that you want a many-to-many between Distributors and ManuFacturers. Entity Framework will created a proper junction table for you, although you won't need it in your queries as I'll show you below. If you don't like the default junction table that entity framework creates for you, you can use fluent API to define the table and column names:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Distributor>()
.HasMany(distributor=> distributor.Manufacturers)
.WithMany(manufacturer => manufacturer.Distributors)
.Map(map =>
{
map.MapLeftKey("DistributorId");
map.MapRightKey("ManufacturerId");
map.ToTable("DistributorsManufacturers");
});
Although internally entity framework will use the junction table, you won't use it in your queries, just use the ICollections:
I've got a _user and I want several properties of the zero or one
Distributor of this user, together with several properties of all the
Manufacturers of this Distributor
Although the Include statement can be used, it is seldom wise to do so. One of the slower parts of database queries is the transfer of the selected data to your process, so you should limit the amount of transferred data to only the properties you really plan to use. Include will transfer all properties, and I highly doubt whether you'll use them, especially all the foreign keys with all the same values.
So your query using the ICollection:
var _user = ... // I've got a User
var result = dbContext.Distributers
.Where(distributor => distributor.Id == _user.DistributorId)
.Select(distributor => new
{
// select only the distributor properties you plan to use
Id = distributor.Id,
Name = distributor.Name,
Address = new
{
// again: only the properties you plan to use
Street = distributor.Address.Street,
City = distributor.Address.City,
Zip = distributor.Address.Zip,
}),
// fetch (all or some) manufacturers of this distributor
Manufacturers = distributor.Manufacturers
.Where(manufacturer => manufacturer.Address.NavigationState == "CA")
.Select(manufacturer => new
{
// select only the properties you plan to use
// probably not the foreign key to the junction table
Name = manufacturer .Name,
Address = new {...},
...
})
.ToList(),
})
.SingleOrDefault();
It might be that you want some different properties, but you get the gist
From what I've read elsewhere, this is not do-able in the format that you are attempting. I still believe I've seen it done, but I didn't like what I saw and now don't recall the details.
My suggestion is that you turn your query around so that you are querying the Distributor (as specified by _user.DistributorId and then .Include() the Manufacturers.
However... I think you'll run into the same problem when querying the AddressesNavigation.State. As you say it is not conceptually different. You may only believe this is working due to seeing an error relating to the condition which comes later in your code - but that's no guarantee of the application of the conditions in the compiled query expression.
I am building an application that has a diary feature, and within that object there are a list of appointments. For display purposes I have a requirement to create a nested groupings in the following format
Heading 1
Sub-heading 1
Data row 1
Data row 2
Sub-heading 2
Data row 1
Data row 2
Heading 2
Sub-heading 1
Data row 1
Data row 2
where heading is the office that the appointment is in, and the sub-heading is the room that this is in. This will make more sense when looking at the following models;
public class Diary
{
public int Id {get; set; }
public DateTime DiaryDate {get; set; }
List<Appointment> Appointments {get; set;}
}
public class Appointment
{
public int Id {get; set;}
public int DiaryId {get; set;}
public DateTime StartTime {get; set;}
public DateTime EndTime {get; set; }
public string Attendees {get; set; }
public Office Office {get; set; }
public Room Room {get; set; }
}
Office is an enum with office locations in, as is Room with rooms in it.
Currently I have solved this problem using this code:
Appointments.GroupBy(k => k.Office, k => k)
.ToDictionary(k => k.Key,
k => k.ToList().GroupBy(sk => sk.Room)
.ToDictionary(mk => mk.Key, mk => mk.ToList()));
I am writing the diary to a view model where the appointments is of type
public Dictionary<Office, Dictionary<Room, List<Appointment>>> Appointments { get; set; }
Essentially, my long group by statement is grouping the the data by Office and then using the resultSelector overload to project the original object. Then, I am turning that IGrouping result into a Dictionary, where the key is office and value is list of type Appointment. From that I am then grouping each list of type Appointment by Room, creating a dictionary within a dictionary, which produces the type Dictionary with Key Office, and then dictionary with key Room and list of Appointment as the value to each key.
This code produces the desired result, but I think it is difficult to read, more difficult to understand in the view when looping and probably quite inefficient.
Can somebody offer some advice as how I can simply the way I achieve my desired result?
You're actually building up a lookup, a dictionary with multiple values per key. Just use that.
Your query simply becomes:
var query = appointments.OrderBy(a => a.Office).ThenBy(a => a.Room)
.GroupBy(a => a.Office)
.ToDictionary(g => g.Key, g => g.ToLookup(a => a.Room));
I use Entity Framework Code First and I have three tables (for example):
public class Motorbike()
{
public int Id {get; set;}
public string Producent {get; set;}
public Engine Motor {get; set;}
public Tire Tires {get; set;}
}
public class Engine()
{
public int Id {get; set;}
public int Power {get; set;}
public decimal Price {get;set;}
}
public class Tire()
{
public int Id {get; set;}
public int Size {get; set;}
public decimal Price {get; set;}
}
It's just example, in fact it's more complicated.
Entity Frmaework generates table for me, but tables Motorbike has column: Id, Power, Engine_Id (where storing only number - id engine, not whole object) and Tire_Id (where storing only number - id tire, not whole object).
I know how to insert data - just create new Motorbike object, save to his fields data (for Engine and Tire fields I save whole objects not only id) and use .Add() method from my context.
But how to get data for row where motorbike id is (for example) 1?
I've tried something like this:
List<Motorbike> motorbikes= new List<Motorbike>();
var list = _context.Motorbike.Where(p => p.Id == 1);
motorbikes.AddRange(list);
but always I've got null for Engine and Tire fields (fields Id and Producent are fill properly).
Use Include to load related entities like:
var list = _context.Motorbike
.Include(m=> m.Engine)
.Include(m=> m.Tire)
.Where(p => p.Id == 1);
See: Entity Framework - Loading Related Entities
You're looking for the Include() method.
List<Motorbike> motorbikes = _context.Motorbike
.Include(p => p.Engine)
.Include(p => p.Tire)
.Where(p => p.Id == 1)
.ToList();