Access included table properties in entity framework - asp.net-mvc

I have two tables:
Table 1 (Sale):
Id | CustomerId
Table 2 (SaleProduct):
Id | SaleId | Quantity
I have the following statement:
var topSales = db.Sales.Include(c => c.SaleProducts).Where(p =>p.CustomerId == id);
I want to sort the SaleProducts table, and select 5 top(ordered by quantity) based on SaleId foreign key, where CustomerId is equal with an id it receives in a controller method.
How can I access the SaleProducts properties in order to do something like this:
var orderedItems = topSales.OrderByDescending(c => c.Quantity).Take(5);
When I include the SaleProducts table I have in there all the properties but I can't accesc them like in the statement above.
Any help is appreciated

You have two options here:
First option.
Try querying SaleProduct table instead of Sale table and Include Sale:
var topSaleProducts = db.SaleProducts
.Where(m => m.Sale.CustomerId == id)
.Include(m => m.Sale)
.OrderByDescending(m => m.Quantity)
.Take(5);
Now you have top 5 sale products according to CustomerId and Sale will alse be loaded.
Second option.
You can use explicit loading to get top 5 SaleProducts after loading Sale. Remember that, you will query database twice by this way:
// Load the sale first
var topSale = db.Sales.First(m => m.CustomerId == id);
// Then load SaleProducts using Explicit loading.
db.Entry(topSale)
.Collection(m => m.SaleProducts)
.Query()
.OrderByDescending(m => m.Quantity)
.Take(5)
.Load();

You can order by when you access the SaleProducts property and use Take method to get 5 items.
var prods2 = db.Sales.Include(c => c.SaleProducts)
.Where(p =>p.CustomerId == id)
.Select(g => new
{
SaleId = g.Id,
Products = g.SaleProducts.OrderByDescending(h => h.Quantity)
.Take(5)
.Select(x => new
{
Id = x.Id,
Quantity = x.Quantity
})
}).ToList();
The above code will give you the Sales for a specific customer and for each customer it will include only the top 5 SaleProduct's (sorted by quantity)
I am projecting the results to an anonymous object ( new { }). If you have a view model, you can change the projection to that.
public class SaleVm
{
public int Id { set; get; }
public int CustomerId { set; get; }
public List<SaleProductVm> SaleProducts { set; get; }
}
public class SaleProductVm
{
public int Id { set; get; }
public int SaleId { set; get; }
public int Quantity { set; get; }
}
Simply replace the annonymous object projection with these classes
List<SaleVm> prods2 = db.Sales.Include(c => c.SaleProducts)
.Where(p =>p.CustomerId == id)
.Select(g => new SaleVm
{
SaleId = g.Id,
Products = g.SaleProducts.OrderByDescending(h => h.Quantity)
.Take(5)
.Select(x => new SaleProductVm
{
Id = x.Id,
Quantity = x.Quantity
})
}).ToList();

Related

How to filter List Inside List using Linq?

I have a View Model
public class TechnicianZipInformation
{
public int id {get; set;}
public virtual Technician Technician { get; set; }
}
Inside this view Model the data Type of Technician is Technician Class
public class Technician
{
public int Id {get; set}
public virtual ICollection<WorkOrder> WorkOrders { get; set; } // Inside work order there is another member id of int.
}
Let's say stepOne is the List of TechnicalZipInformation.
Now I need to filter the list of work order whose id !=52 inside Technician inside List of TechnicianZipInformation.How Can I do this?
I tried by following
var testResult = stepOne.Select(x => x).Where( z=> z.Technician.WorkOrders.Select(x => x.Id != 52)).ToList();
It didn't help how can i do this?
image example :-
You can use the SelectMany Linq function to make a single list of WorkOrders across all TechnicalZipInformations.
List<WorkOrder> result = stepOne.SelectMany(t => t.Technician.WorkOrders.Where(x => x.Id != 52)).ToList();
To remove WorkOrders with Id = 52 you can use use ToList() proceed with ForEach and reassign WorkOrders with Where like below query.
stepOne.ToList()
.ForEach(z => z.Technician.WorkOrders = z.Technician.WorkOrders.Where(x => x.Id != 52).ToList());
To use ForEach, convert your ICollection to List with ToList().
Filter WorkOrders with z.Technician.WorkOrders.Where(x => x.Id != 52).ToList()
Reassign z.Technician.WorkOrders with above filter as .ForEach(z => z.Technician.WorkOrders = z.Technician.WorkOrders.Where(x => x.Id != 52).ToList()).

EF Find entities not in list

I have this entities:
Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
PriceList
public class PriceList
{
public int Id { get; set; }
public string Name { get;set; }
}
PriceListProduct
public class PriceListProduct
{
public int Id { get; set; }
public int PriceListId { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
Question is, how can I get the products not in Price List using LINQ?
My first thought was the use of Contains, but the list of products could be greater than 100000, if Contains is translate as a query like WHERE NOT IN clause, SQL has an approximate limit of 2000 parameters, so besides performance, I think this is not the best approach.
Is there another way? Should I use raw queries?
Update # 1
I'm trying to understand GroupJoin following #Indregaard answer. So far I have this.
var productsWithNoPrice = db.Product()
.GroupJoin(db.PriceListProduct().Where(plp => plp.PriceListId == 2)
.Select(plp => plp.Product),
p => p.Id,
plp => plp.Id,
(p, product) => new { p.Id, Product = product })
.Where(p => !p.Product.Any())
.Select(p => p.Product);
With the filter
.Where(plp => plp.PriceListId == 2)
I'm filtering products from Price List with Id 2. I think this is close but the query generated by SQL returns a number of rows that corresponding to the number of products that not exists in the Price List but every single column is null.
Basically what I need is a query like this
select * from Product p
left join PriceListProduct plp on plp.ProductId = p.Id and plp.PriceListId = 2
where plp.Id is null
So you are looking for Antijoin.
Manual approach could be like this:
var query =
from p in db.Products
join plp in db.PriceListProducts
on p.Id equals plp.ProductId into priceLists
where !priceLists.Any()
select p;
Another way:
var query = db.Products
.Where(p => !db.PriceListProducts.Any(plp => p.Id == plp.ProductId));
But the best way is to create all navigation properties in the model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PriceListProduct> PriceLists { get; set; }
}
and let EF create queries for you
var query = db.Products.Where(p => !p.PriceLists.Any());
Have you tried Join/GroupJoin?
I haven't tried against a database to see if the generated sql is valid/working but against regular objects something like this will work.
var productsWithNoPrices = products.GroupJoin(productPriceList,
product => product.Id,
productprice => productprice.ProductId,
(product, productPrice) => new { Product = product, Prices = productPrice})
.Where(c=>!c.Prices.Any()).Select(c=>c.Product);
Edit: Based on your updated question I think you want something like this:
var productsWithNoPrices = db.Products.GroupJoin(db.PriceListProducts.Where(c => c.PriceListId == 2),
product => product.Id,
productprice => productprice.ProductId,
(product, productPrice) => new { Product = product, Prices = productPrice }).Where(c=>!c.Prices.Any()).Select(c=>c.Product);
GroupJoin will take whats in your left table (db.Products), join with whats in your right table (db.PriceListProducts.xxxxx) (parameter 1): each product from the left table will get a list of matches from right, combined on product ids (param 2 and 3), output to a anonymous type (param 4). Filter all this on where there is no productprice and select the products. This results in the following SQL, which seems to give the desired result?
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[Products] AS [Extent1]
WHERE NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PriceListProducts] AS [Extent2]
WHERE (2 = [Extent2].[PriceListId]) AND ([Extent1].[Id] = [Extent2].[ProductId])
)

Adding nested entities to a database

I'm using MVC 4 and Entity Framework 5 Code First.
I have the following entities:
public class File
{
public string Album { get; set; }
public string Genre { get; set; }
}
public class Genre
{
public string GenreId { get; set; }
public virtual List<Album> Albums { get; set; }
}
public class Album
{
public string AlbumId { get; set; }
}
I try to add them to a database:
private readonly List<File> _files = new List<File>();
var genres = _files
.Select(x => new Genre { GenreId = x.Genre })
.GroupBy(x => x.GenreId)
.Select(x => x.First())
.ToList();
foreach (var genre in genres)
{
var genreId = genre.GenreId;
var albums = _files
.Where(x => x.Genre == genreId)
.Select(x => new Album { AlbumId = x.Album })
.GroupBy(x => x.AlbumId)
.Select(x => x.First())
.ToList();
genre.Albums = albums;
_unitOfWork.GenreRepository.Insert(genre);
}
And I get the following error:
Violation of PRIMARY KEY constraint 'PK_dbo.Albums'. Cannot insert duplicate key in object 'dbo.Albums'. The duplicate key value is (Ooz).
The statement has been terminated.
The data I try to add looks like the following:
--Tribal
----Ooz
--Goa Trance
----Ooz
----Transient Dawn
--Progressive Trance
----Ooz
----Music Prostitute: The Remixes
--Psy-Trance
----Music Prostitute: The Remixes
--Downtempo
----Transient Dawn
--Ambient
----Transient Dawn
I see that I'm trying to add each album many times (because each album has many genres) but I don't know how to solve that issue.
Thanks for help!
When creating the list of albums, try to get an album from the database before creating it.
Try this:
var albums = _files
.Where(x => x.Genre == genreId)
.Select(x => _yourDBContext.Albums.Where(album=>album.AlbumId == x.Album).FirstOrDefault() ??
new Album { AlbumId = x.Album })
.GroupBy(x => x.AlbumId)
.Select(x => x.First())
.ToList();
The ?? is the null-coalescing operator, it chooses the second value if the first is null. I'm not sure, but hope this will help.
Instead of parsing into List<File> just insert into database at that step. Of course, you'll need to look to see if the genre exists, and if it does then insert files under that genre, if not then insert a new genre with the files already in the List<Album> Albums
Since you can do all of this in one context, entity will be very efficient for you. What I mean is that entity will track whether things are in the database or not and you can just build the tree of genres and albums and call one save changes.

Fetch entire entity with joined entities from database and avoid lazy load, nHibernate QueryOver

I have an entity like this:
public class Employment
{
public virtual Company Company {get; set;}
public virtual Person Person {get; set;}
public virtual string Description {get; set;}
}
providing a relationship between two other entities. They have corresponding DTO's and I want to return a result set containing all information on persons and companies. The query is performed on the Employment table and my problem is that Hibernate generates one select statement per company and person.
In my database, The Employment table has 1000 rows. Nhibernate generates 2001 select statements, one for the Employment list and one select for each Person and Company as I map them to DTO's.
I would like hibernate to fetch all information at once, in SQL I would have done something like this:
SELECT e.Description, c.A, c.B, c.C, p.D, p.E, p.F
FROM Employment e
JOIN Company c ON e.Company_Id = c.Company_Id
JOIN Person p ON e.Person_Id = p.Person_Id;
Or even
SELECT Description FROM Employment;
SELECT c.A, c.B, c.C FROM Employment e
JOIN Company c ON e.Company_Id = c.Company_Id;
SELECT p.D, p.E, p.F FROM Employment e
JOIN Person p ON e.Person_Id = p.Person_Id;
I am a pretty fresh user of nHibernate, QueryOver.I welcome Linq-To-Entities answers as well but I prefer to avoid LINQ query expressions.
I've looked all over the web, read about JoinQuery, JoinAlias and Fetch and have come as far as something like this:
//This works, but the objects are retrieved as PersonProxy and CompanyProxy,
//generating 2 SELECT statements for each Employment I map to EmploymentDto
var queryOver =
session.QueryOver<Employment>()
.Fetch(x => x.Person).Eager
.Fetch(x => x.Company).Eager
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.InnerJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.InnerJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
JoinQuery provided the same result aswell. I feel I am missing something important here. Something should be done in the query or before .List() to fetch all child entities instead of loading a list with lots of Employment entities loaded with PersonProxy and CompanyProxy. I can, however, not find out how...
EDIT: Added mapping
Database tables:
TABLE Company(
Id,
A,
B,
C)
TABLE Person(
Id,
D,
E,
F);
TABLE Employment(
Person_Id,
Company_Id,
Description);
Entities
public class Company
{
public virtual string Id { get; set; }
public virtual string A { get; set; }
public virtual bool B { get; set; }
public virtual bool C { get; set; }
}
public class Person
{
public virtual string Id { get; set; }
public virtual string D { get; set; }
public virtual string E { get; set; }
public virtual string F { get; set; }
}
public class Employment
{
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
public virtual string Description { get; set; }
public override bool Equals(object obj)
{
Employment toCompare = obj as Employment;
if (toCompare == null)
return false;
return (this.GetHashCode() != toCompare.GetHashCode());
}
public override int GetHashCode()
{
unchecked
{
int results = Person != null ? Person.GetHashCode() : 0;
results = (results * 397) ^ (Company != null ? Company.GetHashCode() : 0);
results = (results * 397) ^ (Description != null ? Description.GetHashCode() : 0);
return results;
}
}
}
Mapping
public class CompanyMap : SyncableClassMap<Company>
{
public CompanyMap()
{
Table("Company");
Id(x => x.Id).Column("Id").GeneratedBy.Assigned();
Map(x => x.A).Column("A");
Map(x => x.B).Column("B").CustomType<YesNoType>();
Map(x => x.C).Column("C").CustomType<YesNoType>();
}
}
public class PersonMap : SyncableClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id).Column("Id").GeneratedBy.Assigned();
Map(x => x.D).Column("D");
Map(x => x.E).Column("E");
Map(x => x.F).Column("F");
}
}
public class EmploymentMap : ClassMap<Employment>
{
public EmploymentMap()
{
Table("Employment");
CompositeId()
.KeyReference(x => x.Person, "Person_Id")
.KeyReference(x => x.Company, "Company_Id");
Map(x => x.Description, "Description");
}
}
after your edit i see you have a keyreference instead of a normal many-to-one.
Unfortunatly this seems to be a limitation of QueryOver/Criteria which does not eager load keyreferences even with Fetchmode specified. However Linq to NH does not have this limitation. Change the query to
using NHibernate.Linq;
var results = session.Query<Employment>()
.Fetch(x => x.Person)
.Fetch(x => x.Company)
.ToList();
I have experienced the same problem you're describing here. I'll take your last code snippet as an example, since that's the way I've made it work:
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.InnerJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.InnerJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
First of all, you shouldn't have to specify JoinType.InnerJoin since that's the default join type. Same as with you, I've also found that the persons and companies are loaded lazily this way.
However, if you change the join type to JoinType.LeftOuterJoin, you'll see that everything is loaded eagerly. At least that's what I've experienced. So try change your code to the following:
//This works, but the objects are still retrieved as PersonProxy and CompanyProxy,
var queryOver =
session.QueryOver<Employment>()
.JoinAlias(x => x.Person, () => personAlias, JoinType.LeftOuterJoin)
.JoinAlias(x => x.Company, () => companyAlias, JoinType.LeftOuterJoin);
var mapResult = MappingEngine.Map<IList<EmploymentDto>>(queryOver.List());
I can't explain to you why it is this way, I've just found this to work out of own experiences. And if it's problematic for you doing a left outer join, you can instead try to filter accordingly in your code before (or while) doing the mapping.

Entity Framework 4 - How to cap the number of child elements from another table (connected via a foreign key)

I have two data models Blog and Post. BlogId is a foreign key on the Post table
public class Blog
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Post> Posts { get; set; }
...
}
public class Post
{
public int ID { get; set; }
public virtual int BlogId { get; set; }
public string Title { get; set; }
...
}
Now this works fine and my Repository is happy and pulls everything as expected from DB.
My question is - Is there a way to limit the number of Posts that get retrieved. Perhaps some LINQ magic?
Here is what my current method in the repository looks like:
public Business FindBlog(int id)
{
return this.context.Get<Blog>().SingleOrDefault(x => x.ID == id);
}
Unfortunatelly EFv4 doesn't offer easy way to limit number of returned record for navigation property.
If you are using EntityObject derived entities you can use something like:
var blog = context.Blogs
.Single(b => b.Id == blogId);
var posts = blog.Posts
.CreateSourceQuery()
.OrderByDescending(p => p.Date)
.Take(numberOfRecords)
.ToList();
If you are using POCOs you must execute separate query for that (in case of proxied POCOs you can convert navigation property to EntityCollection<Post> to get access to CreateSourceQuery):
var blog = context.Blogs
.Single(b => b.Id == blogId);
var posts = context.Posts
.Where(p => p.BlogId == blogId)
.OrderByDescending(p => p.Date)
.Take(numberOfPosts)
.ToList();
EFv4.1 and DbContext API offers the way to load only limited number of related entities:
var blog = context.Blogs
.Single(b => b.Id == blogId);
context.Entry(blog)
.Collection(b => b.Posts)
.Query()
.OrderByDescending(p => p.Date)
.Take(numberOfPosts)
.Load();
Edit:
You can do it in single query with projection:
var blog = context.Blogs
.Where(b => b.Id == blogId)
.Select(b => new
{
Blog = b,
Posts = b.Posts
.OrderByDescending(p => Date)
.Take(numberOfRecords)
})
.SingleOrDefault()
Just be aware that you must access posts from second paremeter of anonymous type.
Do you mean:
public List<Post> GetBlogPosts(Blog blog, int numberOfPosts)
{
return blog.Posts.Take(numberOfPosts);
}

Resources