This question already has answers here:
LINQ To Entities does not recognize the method Last. Really?
(6 answers)
Closed 3 years ago.
Is there a way to order, let's say, all customers by the date of the last purchase?
For example
ctx.Customers.OrderByDescending(e => e.Purchases.LastOrDefault().DateTime);
It would be something like this, however, this doesn't work. It throws the exception
LINQ to Entities does not recognize the method 'Purchase
LastOrDefault[Purchase]
(System.Collections.Generic.IEnumerable`1[Purchase])'
method, and this method cannot be translated into a store expression
edit:
public class Customer
{
public Customer()
{
Purchases = new List<Purchase>();
}
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public virtual IList<Purchase> Purchases { get; set; }
}
public class Purchase
{
public int Id { get; set; }
public int IdCustomer { get; set; }
public DateTime DateTime { get; set; }
public virtual Customer Customer { get; set; }
}
In Context I do have somthing like
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasRequired(s => s.Customer)
.WithMany(p => p.Purchases)
.HasForeignKey(s => s.IdCustomer);
}
ctx.Customers.OrderByDescending(e => e.Purchases.LastOrDefault().DateTime);
looks like a context query (Entity Framework, usually dbContext), so here you have an IQueryable not a List.
Entity Framework will try to convert this to a SQL statement before giving you results, but
SELECT * BOTTOM(X) FROM TABLE ORDER BY Purchases desc
is not an expression, but more importantly EF just doesn't recognize what you want to do.
Instead, you just want to flip the logic to:
SELECT * TOP(X) FROM TABLE ORDER BY Purchases asc
Or:
ctx.Customers.OrderBy(e => e.Purchases.FirstOrDefault().DateTime);
or you can order by on your subquery:
ctx.Customers.OrderBy(e => e.Purchases.OrderByDescending(x => x.propertyToSortOn)
.FirstOrDefault().DateTime);
Getting the last n records from the bottom of a sorted list, is actually the same as getting the top n from a list sorted the other way:
1,2,3,4,5,6 -> top 3 in ascending order = 1,2,3
6,5,4,3,2,1 -> bottom 3 in descending order = 3,2,1
LastOrDefault is not supported in Linq-to-Entities (meaning they have not yet developed a way to translate that to the equivalent SQL code). One option is to use AsEnumerable to do the ordering in memory:
ctx.Customers
.AsEnumerable()
.OrderByDescending(e => e.Purchases.LastOrDefault().DateTime);
However, since the order of Purchases is not deterministic, you may want to specify an order there as well:
ctx.Customers
.AsEnumerable()
.OrderByDescending(e => e.Purchases.OrderBy(p => p.DateTime).LastOrDefault());
or just use Max on the `Purchases':
ctx.Customers
.AsEnumerable()
.OrderByDescending(e => e.Purchases.Max(p => p.DateTime));
If the performance of any of those queries is not acceptable, the last resort would be to write the direct SQL and pass that to ctx.Customers.SqlQuery()
Related
I have a DbSet class:
public class Manufacturer
{
public Guid Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
I know I can use Skip() and Take() to get limited manufacturers. But my requirement is to get limited Products of all the manufacturers. I'm using something like this but it's not working
var manufacturers = await _context.Manufacturers.Where(x => x.Products.Take(10))
.ToListAsync();
PS: I'm using Lazy Loading (Not eager loading)
Compile error is:
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable<Domain.Product>' to 'bool'
Cannot convert lambda expression to intended delegate type because
some of the return types in the block are not implicitly convertible
to the delegate return type
How can I achieve to get all the manufacturers but limited products in them?
I believe there is no way to do this directly with a queryable source. You can manage it in memory.
var manufacturers = await _context.Manufacturers.Include(m => m.Products).ToListAsync();
foreach(var m in manufacturers)
{
m.Products = m.Products.Take(10).ToList();
}
This will get all products for each manufacturer from the DB and then keep only the first 10.
You can load the Manufacturer entity without the Product list first (so without an Include() call) and then run a separate query to load only the products you want for a specific Manufacturer entity. EF will automatically update the navigation properties. See the following example (authors can have multiple posts in this example):
using (var context = new MyContext())
{
Author author = context.Author.First();
Console.WriteLine(context.Post.Where(it => it.Author == author).Count());
context.Post.Where(it => it.Author == author).Take(2).ToList();
Console.WriteLine(author.Posts.Count());
}
This will generate the following output:
3
2
Even though there are three entries available in my test database, only two are actually read. See the generated SQL queries:
For the Author author = context.Author.First(); line:
SELECT `a`.`Id`, `a`.`Name`
FROM `Author` AS `a`
LIMIT 1
For the context.Post.Where(it => it.Author == author).Count() line:
SELECT COUNT(*)
FROM `Post` AS `p`
INNER JOIN `Author` AS `a` ON `p`.`AuthorId` = `a`.`Id`
WHERE `a`.`Id` = 1
For the context.Post.Where(it => it.Author == author).Take(2).ToList(); line:
SELECT `p`.`Id`, `p`.`AuthorId`, `p`.`Content`
FROM `Post` AS `p`
INNER JOIN `Author` AS `a` ON `p`.`AuthorId` = `a`.`Id`
WHERE `a`.`Id` = 1
LIMIT 2
However, you have to do this trick for each individual Manufacturer entity, that it loads only ten associated Product entities. This can result in 1+N SELECT queries.
Try the longer way:
_await _context.Manufacturers.Select(x =>
{
x.Products = x.Products.Take(10).ToList();
return x;
}).ToListAsync();
I need to load only 5 elements from a list without loading all the list. I have these two entities:
public class Company
{
public int ID { get; set; }
public String Name{ get; set; }
public List<Employee> EmployeeList{ get; set; }
}
and:
public class Employee
{
public int ID { get; set; }
public String Name{ get; set; }
}
I need to load only the last 5 records of the Employee for a company named "CompanyName".
I tried to use :
Company companySearch =systemDB.Companies
.Include("EmployeeList").Take(5)
.Where(d => d.Name.Equals("CompanyName"))
.SingleOrDefault();
But this code loads all the list and after gives me back only the last 5 records. I need a faster query.
PS: It's code first EF
For loading selective N records of EmployeeList you will have to have some criterion based on which the members of your collection navigation property will be filtered. I've taken that criterion as value of ID property of Employee entity. Here are all the steps required along with code snippet which will do the lazy loading of EmployeeList collection for Company entity
Enable lazy loading in constructor of your inherited dbContext class. I believe systemDB is object of a class which inherits from DbContext
public SystemDB()
{
this.Configuration.LazyLoadingEnabled = true;
}
Remove the include clause to avoid eager loading:
Company companySearch =systemDB.Companies
.Where(d => d.Name.Equals("CompanyName"))
.SingleOrDefault();
After execution of this line of code, If you check the EmployeeList property of companySearch object it will be shown as Null in quick watch window.
Perform the lazy loading of EmployeeList property using the below mentioned call. Put explicit criterion for filtering the records. I've the set the filter criteria to restrict the employees whose ID lies between 1 and 5, both boundaries being inclusive.
db.Entry<Company>(companySearch).Collection(s => s.EmployeeList).Query().Where(p => p.ID >= 1 && p.ID <= 5).Load();
Note that it is not currently possible to filter which related entities are loaded. Include will always bring in all related entities. Reference
You could still try anonymous projection without lazyloading
this.Configuration.LazyLoadingEnabled = false;
Anonymous projection.
Company companySearch =systemDB.Companies
.Where(d => d.Name.Equals("CompanyName"))
.Select(x=> new
{
company = x,
employees = x.Employees.Take(5),
}
.FirstOrDefault()
You will get more idea about how to do anonymous projection is here
After some hours of trying and researching I'm still stuck with the following problem.
I have a list of customers, each customer has a list of orders. I now want to sort the list of customers by their highest order number.
Another aspect is that not every customer has an order, so for sorting i want to replace the missing order number with an “-“ sign.
My first approach won’t work because it can’t compare the object, but it explains the problem quite well.
customers.OrderBy(m => m.Orders == null ? "-" : m.Orders.LastOrDefault().OrderNumber.ToString());
After reading the link below I ended up with this code, but that is not working either.
how do I sort a collection with child property?
customers.OrderBy(c => c.Orders.OrderBy(o => o == null ? "-" : o.OrderNumber.ToString()).Select(o => o == null ? "-" : o.OrderNumber.ToString()).ToList();
Can someone tell me how I can do this best?
Best Regards
Edit:
Here are the entities (not full entity) I'm trying to sort.
I'm working with entity framework and I've already read out the customers as an IQueryable and now I need to sort this.
Customer
public class Customer
{
public int ID { get; set; }
public virtual List<Order> Orders { get; set; }
}
Order
public class Order {
public int ID { get; set; }
public int OrderNumber { get; set; }
public virtual Customer Customer { get; set; }
}
Now the customer does not necessarily have a list of orders associated.
Meaning a customer can have 0,1 or n orders assigned to him, that's what I meant with null.
I now want to sort the list of customers by their highest order number.
Then the following should do the trick:
var result = customers.OrderBy(c => c.Orders.Max(o => (int?)o.OrderNumber));
The int? cast is needed to let Max function return null for customers w/o orders. This normally would put them at the beginning of the sort order.
In case you want to put customers w/o orders at the end, the simplest would be to use int.MaxValue (assuming you have no OrderNumber == int.MaxValue):
var result = customers.OrderBy(c => c.Orders.Max(o => (int?)o.OrderNumber) ?? int.MaxValue);
You could first remove the customers with no Orders and then add them back with Concat()
customers
.Where(c => c.Orders.Count() != 0)
.OrderBy(c => c.Orders.OrderBy(o => o.OrderNumber).Select(o => o.OrderNumber).FirstOrDefault())
.Concat(customers.Where(c => c.Orders.Count() == 0))
.ToList();
Let me know if that works for your use-case.
So Long
I have an entity that looks like this:
public class Entries
{
public Contract Contract { get; set; }
public int Year { get; set; }
}
public class Contract
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to be able to query the database and return an object that would allow me to report on the Contract data like this where the numbers for each year are counts for that year:
Contract ID/Name 2015 2016
1 - ABC 12 4
2 - XYZ 17 76
3 - QRS 414 0
I've started with Linq like this:
var results = context.Entries
.Include(x => x.Contract)
.GroupBy(x => new { x.Contract, x.Year })
.Select(x => new { Contract = x.Key.Contract, Year = x.Key.Year, Count = x.Count() })
.OrderBy(x => x.Contract.Number)
.Take(5).ToList();
I would like this single IQueryable to set me up to push the data into an object that mimics the table above. I'm having trouble setting it up, though, because of the Take(). I only want to show the first 5 results, but when I apply it like I've done above after grouping, using the example data given, there will be one record for ABC 2015 and one record for ABC 2016. I'm not sure the best way to use GroupBy() and Take() together to accomplish my goal.
You need a nested collection:
var results = context.Entries
.Include(x => x.Contract)
.GroupBy(t=>t.Contract)
.Select(t=>new{
Contract=t.Key,
Years=t.GroupBy(s=>s.Year)
.Select(s=>new{
Year=s.Key,
Count=s.Count()
})
})
.Take(5);
You'll have a IEnumerable of pairs of Contract/Years where Years is an IEnumerable of year / count pairs with the counts per year.
I am trying to build a select query to a table while only contains concatenated Primary Key values and no attributes. I managed to get the select query alright, but I can't quite figure out how to get the select statement. Basically I am trying to get a list of DegreeID's, from the DegreeRelationship table, (which is mapped and not an entity) from a ProgramID in DiplomaCertificate entity. Then I want to get the Degree name as well.
My context with the mapped tables looks like this:
modelBuilder.Entity<Degree>()
.HasMany(e => e.DiplomaCertificates)
.WithMany(e => e.Degrees)
.Map(m => m.ToTable("DegreeRelationship").MapLeftKey("DegreeID").MapRightKey("ProgramID"));
and basically, I am trying to put values into this object:
public class DegreeRelationshipInfo
{
public int ProgramID { get; set; }
public int DegreeID { get; set; }
public string LinkedProgramName { get; set; }
}
I am trying a method something like this, but I am not sure how to write this exactly (and this is completely wrong):
[DataObjectMethod(DataObjectMethodType.Select, false)]
public List<DegreeRelationshipInfo> Select_DegRel(int programID)
{
using (Pathway_Model context = new Pathway_Model())
{
var results = from data in context.Degrees
where data.DiplomaCertificates.Where(x => x.ProgramID == programID)
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
// no idea how to get this value....
};
return results.ToList();
}
}
Any help would be appreciated!
Select the entities by SelectMany and collect their key values:
from data in context.Degrees
from cert in data.DiplomaCertificates
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
DegreeID = cert.DegreeID,
LinkedProgramName = data.Name // I guess...
}
This from - from construction is compiled into SelectMany, but the syntax is much better readable.