How to write more efficient linq to entities query using EF6 - performance

I have an one-to-many relation in my entities:
public class Header
{
public Header()
{
Items = new List<Item>
}
public int Id {get; set;}
public virtual ICollection<Item> Items {get; set;}
// other properties
}
public class Item
{
public int Id {get; set;}
public virtual Header Header { get; set; }
public string Title { get; set; }
public DateTime CreationDate { get; set; }
public int Weight { get; set; }
}
I want to load Header and some of its Items, so I wrote this linq to entity query(EF6):
using(var ctx = new MyContext())
{
var result = ctx.Headers.Where(someConditions)
.AsNoTracking()
.Select(header => new {
HeaderId = header.Id,
//fetch other header properties here
LastItemCreationDate = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.Title)
.FirstOrDefault(),
LastItemTitle = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.CreationDate)
.FirstOrDefault(),
LastItemWeight = header.Items.OrderByDescending(item => item.CreationDate)
.Select(item => item.Weight)
.FirstOrDefault()
}).ToList();
}
This query generate a sql script with 3 times join Header and Item tables, is there any more efficent way to write this query to join Header and Item tables one time?

Since you are using Select, you don't need AsNoTracking since the resulting query will not load any entities. The key performance impacts in your case would be indexes in the Header table suitability for your Where clause, then also whether there is a Descending Index available on the CreationDate in the Items table.
Another improvement would be to alter the projection slightly:
var result = ctx.Headers.Where(someConditions)
.Select(header => new {
HeaderId = header.Id,
LatestItem = header.Items
.OrderByDescending(item => item.CreatedDate)
.Select(item => new
{
Title = item.Title,
CreationDate = item.CreationDate,
Weight = item.Weight
}).FirstOrDefault()
}).ToList();
This will change the resulting anonymous type structure a bit, but should result in a nicer, single join.
You will get a result.HeaderId, then a null-able result.LastestItem containing the latest item's properties. So result.LatestItem?.Title instead of result.Title.

Related

Linq query to select any in list against a list

Using EF Core code-first, and I want to find any record with a similar list of a foreign entities to the entity I already have.
public class ClownModel {
public int Id { get; set; }
public List<CarModel> Cars { get; set; }
}
public class CarModel {
public int Id { get; set; }
}
var MyClown = new ClownModel() { /*add properties*/ }
//or maybe an existing record selected from database, just some ClownModel instance
Basically, "Select all the ClownModels where they have any Cars.Id that are in my MyClown.Cars"
Assuming that ClownModel has unique CarModel Id's, you can use the following query:
Matches All Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Count() == ids.Count
select cm;
Matches Any Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Any()
select cm;

LINQ Lazy load or query incorrect

LINQ Query not populating
Model extract is as follows
public class ServiceBulletin
{
[Key]
public int Id { get; set; }
public virtual ICollection<ServiceBulletinProducts> ApplicableProducts { get; set; }
}
public class ServiceBulletinProducts
{
[Key]
public int Id { get; set; }
public int ServiceBulletinId { get; set; }
public virtual Product Product{ get; set; }
}
I'm using the following code at the moment to populate a collection
var x = from m in _dc.ServiceBulletins.Include(p => p.ApplicableProducts)
.Include(m => m.Manufacturer)
where m.DeleteStatus == DeleteStatus.Active
select m;
var x1 = new List<ServiceBulletin>();
foreach (var item in x)
{
var p = from m1 in _dc.ServiceBulletinsProducts.Include(p2=>p2.Product)
where m1.Product.DeleteStatus == DeleteStatus.Active &&
m1.ServiceBulletinId == item.Id
select m1;
var p99 = p.ToList();
item.ApplicableProducts = p99;
x1.Add(item);
};
So this is intended to have a Parent Child relationship and I’m trying to do a query which populates a collection of ServiceBulletins with a ApplicableProducts item with a fully populated collection of ServiceBulletinProducts for the ServiceBulletin with the values of the Product populated
The collection is populated but the ServiceBulletinProducts are always set to null and I can’t seem to add an Include such as .Include(p => p.ApplicableProducts.Products) to try and populate the product details – which is resulting in me iterating around the collection to populate the items.
Am I missing something to enable the population on the 1st query for the Include statement or do I need to do the query in a different way ?
Figured out the following should do the trick.
var x = from m in _dc.ServiceBulletins.Include(p => p.ApplicableProducts.Select(p2=>p2.Product))
.Include(m => m.Manufacturer)
where m.DeleteStatus == DeleteStatus.Active
select m;

LINQ Query - How i can make my query better?

I have written following LINQ query to return list and then iterating list separately to convert time into hours and taking sum of hours for each list item.
I am sure this might not be the right away as it send three database calls.
Can i re-write it in a better way e.g. GroupBy or by some other way to assign data to model, converting IdleTime into hours and than taking sum of IdleTime??
Model Class
public class TestModel
{
public string TaskSummary { get; set; }
public string LocationName { get; set; }
public float IdleTime { get; set; }
public string Description { get; set; }
public float IdleTimeSum { get; set; }
public Guid TaskId { get; set; }
}
LINQ Query
List<TestModel> list = _context.Time
.Include(x => x.Report)
.Include(x => x.Report.Task)
.Include(x => x.Report.Task.Location)
.Where(x => taskIds.Contains(x.Report.TaskId))
.Select(x => new TestModel
{
TaskSummary = x.Report.Task.Summary,
LocationName = x.Report.Task.Location.Name,
IdleTime = x.Duration,
Description = x.Description,
TaskId = x.Report.TaskId,
}).ToList();
How i am converting into hours?
foreach (var item in list)
item.IdleTime = item. IdleTime / 60;
How I am taking sum?
foreach (var item in list)
item.IdleTimeSum = item. IdleTime;
Just add those to your projection:
.Select(x => new TestModel
{
TaskSummary = x.Report.Task.Summary,
LocationName = x.Report.Task.Location.Name,
IdleTime = x.Duration / 60.0f,
Description = x.Description,
TaskId = x.Report.TaskId,
NPTHoursSum = x.Duration / 60.0f,
}).ToList();
Although since you're not showing where you actually sum anything I suspect there's more to the problem than that.
Yeah Select is a projection function, you can just do those operations in your select.
.Select(x => new TestModel
{
TaskSummary = x.Report.Task.Summary,
LocationName = x.Report.Task.Location.Name,
IdleTime = x.Duration /60.0f,
NPTHoursSum = x.Duration / 60.0f,
Description = x.Description,
TaskId = x.Report.TaskId,
}).ToList();
If you're using LINQ to SQL then the division operation will probably occur at the db. If you actually want to do a sum it would have to be in a different query or as a sub query or something.\

Dynamic Column Name in LinQ

I am having a class Item.
class Item{
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public string Name { get; set; }
public string Description { get; set;}
}
I want to filter list of items based on dynamic column name.
Suppose I want list of Names then Column Name is "Name" and result will be list of names
If column name is Description, I need list of descriptions.
How to do this with LinQ?
Easy, just select the property you need from the list:
var items = new List<Item>();
//get names
var names = items.Select(x => x.Name);
//get descriptions
var descriptions = items.Select(x => x.Description);
Update:
You'll need a bit of reflection to do this:
var names = items.Select(x => x.GetType().GetProperty("Name").GetValue(x));
Throw this in a method for re-usability:
public IEnumerable<object> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x => x.GetType().GetProperty(columnName).GetValue(x));
return values;
}
Of course this doesn't validate wether the column exists in the object. So it will throw a NullReferenceException when it doesn't. It returns an IEnumerable<object>, so you'll have to call ToString() on each object afterwards to get the value or call the ToString() in the query right after GetValue(x):
public IEnumerable<string> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x => x.GetType().GetProperty(columnName).GetValue(x).ToString());
return values;
}
Usage:
var items = new List<Item>(); //fill it up
var result = GetColumn(items, "Name");

Select multiple columns in LINQ

I've written a LINQ query shown below :
List<Actions> actions = resourceActions.Actions.Select(s => s.ActionName).ToList();
How do I give for selecting multiple columns here ? ie I want to add columns s.ActionId and s.IsActive. I'm unable to apply it.
Make a class to represent the data you want:
public class ResourceAction
{
public int Id {get;set;}
public string Name {get; set; }
}
Select a list of those instead:
List<ResourceAction> actions = resourceActions.Actions
.Select(s => new ResourceAction() { Id = s.Id, Name = s.ActionName}).ToList();
I believe this is what your looking for. However you need to change the output to an anonymous type.
var actions = resourceActions.Actions.Select(s => new { s.ActionName, s.ActionId, s.IsActive } ).ToList();
You can use a anonymous type for this, for example
var actions = resourceActions.Actions.Select(s =>
new { Id = s.Id, Name = s.ActionName, Active = s.IsActive).ToList();
but a better way would be to create a class like
public class ActionWithId
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
List<ActionWithId> actions = resourceActions.Actions.Select(s =>
new ActionWithId() { Id = s.Id, Name = s.ActionName, Active = s.IsActive }).ToList();

Resources