EF Find entities not in list - asp.net-mvc

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])
)

Related

Access included table properties in entity framework

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();

How do I loop through an array and add each value to my model variable? MVC Razor

I am trying to make a variable in my model that retrieves all available skills to later display them in a DDL. I have one variable that contains all of the skills and one variable that displays all of the skills an employee has already rated. I want to check all of the skills against the rated skills so that only the unrated skills are added to the new variable in the model. The only way I could think of to do this is to use a foreach loop to check for the values in the AllSkills variable.
model:
public IEnumerable<long> AvailableSkills { get; set; }
public IEnumerable<long> AllSkills { get; set; }
public IEnumerable <long> EmployeeRatings { get; set; }
public IEnumerable<Rating> Ratings { get; set; }
public IEnumerable<Skill> Skills { get; set; }
public long SkillId { get; set; }
Controller:
model.AllSkills = db.Skills.OrderBy(s => s.SkillName).Select(s => s.SkillId);
model.EmployeeRatings = db.Ratings.Where(r => r.EmployeeId == User.Identity.Name.Remove(0, 8)).OrderBy(s => s.SkillId).Distinct();
foreach (var skill in model.EmployeeRatings)
{
model.AvailableSkills = model.AllSkills.Where(s => s != skill);
}
I want to do something like the following code in the foreach loop:
model.AvailableSkills += model.AllSkills.Where(s=> s != skill); but this is not allowed.
The problem is that this for each loop above is assigning the model.AvailableSkills to the every skill but the last one that is in the foreach loop (as it should). How do I make it so that every one of the duplicate skills are excluded from model.AvailableSkills?
Using LINQ is IMO more readable if you use to query syntax opposed to the method chaining syntax.
model.AllSkills =
from skill in db.Skills
orderby skill.SkillName
select skill.SkillId;
model.EmployeeRatings =
from rating in db.Ratings
let employeeId = User.Identity.Name.Remove(0, 8)
where rating.EmployeeId == employeeId
orderby rating.SkillId
select rating.SkillId
You can use the Except() extension method to remove items from the collection.
// exclude the 'owned' skils
model.AvailableSkills = model.AllSkills.Except(model.EmployeeRatings);
And you probably want to distinct the results:
model.AvailableSkills = model.AvailableSkills.Distinct();
Last but not least:
Because you select the SkillId I'm unsure why you would order the results. This does not make sense especially because you order by different properties in both lists. Furtermore you probably want to select more details to display to the user, but to know this we need more details on your model.
Assuming EmployeeRatings contains rated skills, and you want AvailableSkills to have only skills not in EmployeeRatings but are in AllSkills, I think this is what you like to do:
model.AvailableSkills = model.AllSkills
.Where(s => !model.EmployeeRatings.Contains(s));
Consider two classes like this:
public class Skill
{
public int Id { get; set; }
}
public class Employee
{
public int[] RatedSkills { get; set; }
}
var skills = new List<Skill>
{
new Skill{ Id = 1}, new Skill{Id = 2}, new Skill{ Id = 3}, new Skill { Id = 4}
};
var emp = new Employee
{
RatedSkills = new int[] { 1,2 }
};
var availableSkills = skills.Select(s => s.Id).Except(emp.RatedSkills);
Console.Read();
The rating has an Id property an an employee has an int[] to hold his/her selected ratings. From there it's easy to filter
Following your current approach, you can use AddRange with a list. Something like:
List<long> availableSkills = new List<long>();
foreach (var skill in model.EmployeeRatings)
{
availableSkills.AddRange(model.AllSkills.Where(s => s != skill));
}
model.AvailableSkills = availableSkills;
Or you can achieve this with a more compact approach, and I believe Except removes dupes:
model.AvailableSkills = model.AllSkills.Except(model.EmployeeRatings);

How to create a simple left outer join in lambda expression in EF ASP.NET MVC5

I searched hours and hours for this without any luck. I'm trying to create a lambda expression to fetch data from two tables Schedule and Request. But i'm outputting a bool here. How can i do a proper left outer join to fix this?
this is the best i could come up with
ViewBag.RequestList = db.Requests
.Include(r => r.Department)
.Select(r => db.Schedules.Any(s => s.RequestId == r.RequestId));
but its a bool not a list.
Assume my table models are as follows
public class Request{
public virtual int RequestId { get; set; }
public virtual string Remarks { get; set; }
}
public class Schedule{
public virtual int ScheduleId{ get; set; }
public virtual string Name{ get; set; }
public virtual Request Request { get; set; }
}
I'm trying to see if each and every request has one or more schedules associated with it or not. so if i could attach schedule object to request and output it as a list then thats all i need.
But I want to do it using LINQ and lambda expressions and I've seen queries as below;
var leftList = (from emp in db.Requests
join d in db.Schedules
on emp.RequestId equals d.RequestId into output
from j in output.DefaultIfEmpty()
select new { RequestId = emp.RequestId,
name = emp.Department.Name,
route = emp.Route.Name });
But that's not what i want, because i have to specify every field i need in new { RequestId = emp.RequestId, name = emp.Department.Name, route = emp.Route.Name }
Thanks a lot!
just list what you want like this:
var leftList = from emp in db.Requests
join d in db.Schedules
on emp.RequestId equals d.RequestId into output
from j in output.DefaultIfEmpty()
select new
{
RequestId = emp.RequestId,
name = emp.Department.Name,
route = emp.Route.Name,
ScheduleId=j==null?0:j.ScheduleId,
SName=j==null?""j.Name,
};

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