Linq Query where related entity contains value from array - c#

I have a Model called JobReport which looks like this (simplified)
public class JobReport
{
public JobReport()
{
WorkOrders = new List<WorkOrder>();
}
public int JobID { get; set; }
public List<WorkOrder> WorkOrders{ get; set; }
}
public class WorkOrder
{
public WorkOrder()
{
Total = 0;
}
public string Trade { get; set; }
public int WorkOrderID { get; set; }
public decimal? Total { get; set; }
}
I'd like to run a Linq query which gets me all the Jobs that have WorkOrders that have a trade which is in a passed array.
var trades = new string[] { }
I've tried something like the following which doesn't work, as it tries to get me a list of workorders, when I actually need the underlying jobs.
The problem appears to be because I'm calling Select
var jobsDB = db.Jobs.Include(x=>x.WorkOrders).ToList();
var jobs = (from p in jobsDB
select new JobReport()
{
JobID = p.JobID,
WorkOrders = p.WorkOrders.ToList()
}
jobs = jobs
.Select(x => x.WorkOrders
.Where(y => trades.Contains(y.Trade)));

This will work:
jobs = jobs
.Where(x => x.WorkOrders.Any(y => trades.Contains(y.Trade)));
The way I usually tackle these problems is that I look at what the outcome must be (a list of jobs) - that means we need to put the Where first, and we must look for a condition for a job to be included. It's a bit like constructing a SQL query - in fact you can use the SQL like query syntax for most LINQ tasks, if you prefer.

Related

Combine 2 results into one in Entity Framework Core

I have 2 queries as follows:
var q1 = await context.Submissions
.Include(s => s.Application)
.ToListAsync();
// q1 is of type List<Submissions>
var q2 = await context.Applications
.Select(a => new Application
{
Id = a.Id,
Member = a.Histories.OrderByDescending(h => h.ModifiedDate).FirstOrDefault().Member
}).ToListAsync();
// q2 is of type List<Applications>
Is there a way to combine these 2 queries and have the type as List<Submissions>?
Note: I'm using EF Core version 3
Submissions class:
public class Submission
{
public Guid Id { get; set; }
public string Name { get; set; }
public Application Application { get; set; }
public Guid? ApplicationId { get; set; }
}
Applications class:
public class Application
{
public Guid Id { get; set; }
public string Member { get; set; }
public ICollection<History> Histories { get { return _Histories; } set { _Histories = value; _currentMember =null; } }
private ICollection<History> _memberHistories;
private MemberHistory _currentMember = null;
}
There are .Include() and .ThenInclude()
var q1 = context.Submission
.Include(submission => submission.Application)
.ThenInclude(application = > application.Histories);
having too many includes can give performance issues, unless you actually start splitting up the query. Another approach would be to contain it in a select statement which often performs better. but gives sort of a split result.
var q2 = context.Submissions.Select(submission => new
{
SubMission = submission
Application = submission.Application
});
var result = q2.ToList().Select(t => t.Submission);
due to the built in EF Core mapper, the relations are handled for you so application are loaded and "attached" correctly to the Submission set on the result.

How can I get the count of a list in an Entity Framework model without including/loading the entire collection?

I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]

Write own functions to be used in c# linq statement

I have an object
public class Product{
public int Id { get; set; }
public double Cost { get; set; }
public DateTime DatePurchased { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
public string Category { get; set; }
}
I also have a function public void TotalProductDiscountHistory(DateTime datePurchased, double cost) that does some complex maths and return the discount values from the day it was purchased to date.
I would like to basically be able to call this function inside my query statement to return the sum of all possible for all products as show below.
var query = db.Products.where(x => x.clientId == clientId)
.GroupBy(c => c.Category).Select(a => new {
Category = a.Category,
Cost = a.Sum(u => u.Cost),
TotalDiscounts = a.Sum( TotalProductDiscountHistory(a.DatePurchased,
a.Cost))
});
My questions is how do I achieve this or rather how do I create a function so that I am able to call it within a linq query statement, pass values and return a result.
Your main problem is that the EF is trying to convert the LINQ query into SQL. As there is no SQL equivalent of your function, it really has to just pluck out the data needed for that calculation, and do it after the SQL query, in code.
Linq2SQL was excellent as handling that automatically. EF (even after 6 versions), not so much.
So, we'll have to do it manually:
public double TotalProductDiscountHistory(DateTime datePurchased, double cost) {...}
class CategoryTotals
{
public int Category {get; set;}
public double Cost {get; set;}
public double TotalDiscounts {get; set;}
}
var query = from p in db.Products
where P.clientId == clientId
group p by p.Category;
var totals = new List<CategoryTotals>();
foreach(var grp in query)
{
var ct = new CategoryTotals
{
Category =grp.Category,
Cost = grp.Sum(u => u.Cost),
TotalDiscounts = grp.Sum(u=>
TotalProductDiscountHistory(u.DatePurchased, u.Cost))
};
totals.add(ct);
}

Assign aggregate result to the entity property not pulling all subquery rows

I have a Comment and Votes related to the comment.
[Table("QAComment")]
public class QaComment : IEntity
{
[Key, Column("QACommentID")]
public int Id { get; set; }
// ...
public virtual ICollection<QaCommentVote> Votes { get; set; }
[NotMapped]
public int OverallVote { get; set; }
}
[Table("QACommentVote")]
public class QaCommentVote : IEntity
{
[Key, Column("QACommentVoteID")]
public int Id { get; set; }
[ForeignKey("QAComment")]
public int QaCommentId { get; set; }
public int Value { get; set; }
public virtual QaComment QaComment { get; set; }
}
I need to get comments with the sum of their votes, not pulling all votes to the application.
The ways I can see to achive this:
1. Make a database view for Commment and calc votes sum in there.
Cons: dont wanna make extra-views
2. Via LINQ:
var comments =
Set<QaComment>()
.Select(c => new QaComment() {/* assign every property once again and calc OverallVote */});
Cons: don't like to assign allproperties once again.
Is there a better way devoid of that cons?
UPDATE
This is what I want as a result of LINQ:
SELECT
qac.*,
(SELECT SUM(v.Value)
FROM QACommentVote v
WHERE v.QACommentID = qac.QACommentID) as OverallVote
FROM QAComment qac
You can fetch QaComment and the sum you're looking for separately as anonymous type and merge them into one object using LINQ to Objects:
var comments
= Set<QaComment>()
.Select(c => new { c, sum = c.Votes.Sum(v => v.Value))
.AsEnumerable() // to make next query execute as LINQ to Objects query
.Select(x => { x.c.OverallVote = x.sum; return x.c; })
.ToList();
But to make point clear: I haven't tested that :)

Joining two tables with one to many relatipnship in entity framework code first

i have
public class Menu
{
public int ID { get; set;}
public List<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public byte[] Image { get; set; }
public string Name { get; set; }
}
i would like to know all tasks which has a certain List ID using LINQ queries
Try
var result = Menus.Where(menu => menu.ID == id)
.Select(menu => menu.Tasks)
.FirstOrDefault();
Also you may want to peruse http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b as this would answer most of your queries like the above.
You can use Enumerable.Where
var list = Tasks.Where(l=>l.ID ==x);
or
var list = from t in Tasks
where t.ID == x
select t;
x will be the id you need to compare

Categories

Resources