Where to place AsNoTracking on a Entity Framework query? - entity-framework-core

I have the following Entity Framework Core 2.2 query:
IQueryable<Job> jobs = _context.Jobs.AsNoTracking();
jobs = jobs.Where(x => x.Active);
jobs.Skip(offset).Take(limit);
var result = jobs
.Select(x => new {
Id = x.Id,
Country = new {
Code = x.Country.Code,
Name = x.Country.Name
},
JobTypes = _context.JobTypes.Select(x => x.Name),
Created = x.Created,
Skills = x.JobSkills.Select(y => new {
Id = y.Skill.Id,
Name = y.Skill.Name
})
});
return await result.ToListAsync();
I am using AsNoTracking() to make the query faster. The question is:
Is there a specific place where to place AsNoTracking()?
Should I place it before ToListAsync instead of in the beginning?

Related

How to optimize the query - Ef core

I am working a asp .net core web API with EF core.
I wrote this query. But this take 20-30seconds to execute.
Anyone have idea to improve this query.
var hotels = await _context.Hotels
.Where(i => (i.DestinationCode == request.Destination))
.Select(i => new HotelListHotelVm
{
Item1 = i.Item1,
Item2 = i.Item2,
Item3 = i.Item3,
Item4Code = i.Item4Code,
Item4Description = i.Item4.TypeDescription,
Item5 = i.Item5.Select(x => new HotelListHotelVm.HotelListItem5Vm
{
Code = x.Item5Code,
Description = x.Item5.Description,
}).Where(x =>(incomingItem5s.Length > 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")),
Item6 = i.Item6.Select(x => new HotelListHotelVm.HotelListHotelItem6Vm
{
Id = x.Id,
Item6TypeCode = x.Item6TypeCode,
Order = x.Order,
Path = x.Path,
VisualOrder = x.VisualOrder,
}).Take(3),
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode
}),
})
.Where( i => ((incomingItem4.Length > 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != "")) )
.OrderByDescending(i => i.Code)
.PaginatedListAsync(request.PageNumber, request.PageSize);
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach( var fac in _context.Facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
I f I remove those foreach code, The query takes 8-10s to execute.
But I need those foreach codes. Because I need the HotelFacilityDescription
Any suggestion for optimize the query ?
Edit The i.Facilities - model
public class HotelFacility
{
// removed some
public int FacilityCode { get; set; }
public int FacilityGroupCode { get; set; }
public FacilityGroup FacilityGroup { get; set; }
public int HotelCode { get; set; }
public Hotel Hotel { get; set; }
}
}
_context.Facilities will be enumerated (i.e. database will be called) for every iteration of previous loops. The quick fix is to call it ones and store results in variable:
var facilities = _context.Facilities.ToList();
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach(var fac in facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
Next improvement can be converting facilities into Dictionary for searching purposes.
Even better approach can be writing query joining with _context.Facilities on database side (but here more info needed).
I've read this a couple times, but it looks like the relationship for Hotel.Facilities is a Facility, so could you not just do:
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.Description
}),
If for some reason Hotel.Facilities is not pointing at a Facility, but is a Many-to-Many HotelFacilityGroup entity to a FacilityGroup, that also contains a FacilityCode, if the associated FacilityGroup has access to a set of Facilities beneath it you could leverage that:
Edit: It sounds like multiple Facilities share the same Code where some may have a null description. Provided that the facilities matching the code would be within the same facility group and not consider the same Code within different facility groups. If you need to match the code across all facilities then there probably isn't much of an alternative to loading the entire set of facility codes & descriptions.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f => f.Code == x.FacilityCode && f.Description != null).Select(f => f.Description).FirstOrDefault()
}),
That would avoid the need to go load all of the facilities to resolve that code. Otherwise, if you do need to fetch across all facilities, pre-loading them would be the way to go, but rather than fetching the entire Facility entity I would recommend just the values you need, the Code and the Description. This cuts down on the amount of memory needed and potentially be a faster query:
var facilities = _context.Facilities
.Select(f => new
{
f.Code,
f.Description
}).ToList();
Edit:
From there, finding a match using:
foreach( var facility in hotels.Items.SelectMany(x => x.HotelFacilities)
{
facility.HotelFaciltyDescription = facilities
.Where(x => x.Code == facility.FacilityCode
&& !string.IsNullOrEmpty(x.Description)
.Select(x => x.Description)
.FirstOrDefault();
}
I would recommend an OrderBy clause to ensure the selection of the facility is predictable as it sounds like there could be multiple matches on a code with a non-null description.
The loop can be eliminated by projecting the value in the LINQ to Entities query.
It would have been quite easy if you had relationship and navigation property like other *Code fields. But as clarified in the comments, there is no such relationship, so you have to resort to old good manual left other join to emulate what navigation property provide automatically, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
// left outer join with Facilities
.SelectMany(x => _context.Facilities
.Where(f => x.FacilityCode == f.Code).DefaultIfEmpty(),
(x, x_Facility) => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x_Facility.Description // <--
}),
Here x_Facility emulates optional reference navigation property x.Facility if existed.
In case you need just single property from the related table, instead of a left join you could also use the original query with single value returning correlated subquery inside the projection, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
.Select(x => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = _context.Facilities
.Where(f => x.FacilityCode == f.Code)
.Select(f => f.Description)
.FirstOrDefault() // <--
}),
or even
HotelFacilityDescription = _context.Facilities
.FirstOrDefault(f => x.FacilityCode == f.Code).Description
All these will eliminate the need of the post loop executiong additional database queries. You can test them and take the one with best performance (#2 and #3 produce one and the same SQL, so it's a matter of taste - the choice is between #1 and #2/3).

UpdateRange method of Entity Framework Core does not work

The UpdateRange method of Entity Framework Core is used here to update multiple records but it is not working.
My code is:
var dept1 = new Department()
{
Id = 8,
Name = "New Designing"
};
var dept2 = new Department()
{
Id = 9,
Name = "New Research"
};
var dept3 = new Department()
{
Id = 102,
Name = "New HR"
};
List<Department> modifiedDept = new List<Department>() { dept1, dept2, dept3 };
using (var context = new CompanyContext())
{
context.UpdateRange(modifiedDept);
await context.SaveChangesAsync();
}
And the error I get is:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.'
What should be done in this case?
You are supposed to get data from database and modify data. Not creating new class.
using (var context = new JobContext())
{
var depts = context.Department.Where(x => x.Id > 1).AsQueryable();
depts.Where(x => x.Id == 2).FirstOrDefault().Name = "New Designing";
depts.Where(x => x.Id == 3).FirstOrDefault().Name = "New Research";
depts.Where(x => x.Id == 4).FirstOrDefault().Name = "New HR";
context.UpdateRange(depts);
context.SaveChanges();
}
Before
After

Prevent sort result of union in entity framework

In SQL server union, result is sorted based on primary key column. I want to prevent this behavior in entity framework.
In this post, #praveen has explained how to do this in pure sql. But I want to do this in entity framework.
My code:
public virtual ActionResult Search(string keyword)
{
var products = _db.Products
.Where(x => x.IsActive)
.AsQueryable();
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartTitle = products.Where(x => x.Title.StartsWith(keyword));
var productContainsTitle = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var productList = productExactlyTitle.Union(productStartTitle)
.Union(productContainsTitle)
.Take(10)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = x.Price.ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
// some code ...
}
I want to show records with below order:
First: records of productExactlyTitle
Second: records of productStartTitle
Third: records of productContainsTitle
But result is sorted with Id column! and I don't want this.
Is there a way for do this?
In SQL all queries without an order by explicitly set is considered unordered. (and EF queries a translated into SQL). So if you want a specific order after your union just specify it.
var result = q1.Union(q2).OrderBy(x => x.?);
For your specific case:
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartTitle.Select(x => new { Item = x, Order = 2 });
var p3 = productContainsTitle.Select(x => new { Item = x, Order = 3 });
var productList = p1.Union(p2)
.Union(p3)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Take(10);

Single database call pulling data from multiple tables in EF Core

The following code currently opens a connection three times to my database, to pull out each object.
Is there a better way to craft the query so the database is only hit once and pulls back all the objects I'm looking for?
var metadataResult = new MetadataViewModel
{
Milestones = goalsContext.Milestones.Select(m => new MilestoneViewModel
{
Id = m.Id,
Name = m.Name,
Year = m.Year,
Date = m.Date
}),
Aggregates = goalsContext.Aggregates.Select(a => new AggregateViewModel
{
Id = a.Id,
Name = a.Name
}),
Metrics = goalsContext.Metrics.Select(m => new MetricViewModel
{
Id = m.Id,
Name = m.Name,
Description = m.Description
})
};
If your view models are a fairly similar shape then you should be able to use Union to get everything in one query and then transform the rows into appropriate ViewModel instances afterwards. Something like the following -
var combinedResults =
context.Products.Select(p => new
{
Type = "Product",
ID = p.ProductID,
Name = p.ProductName,
SupplierName = p.Supplier.CompanyName
})
.Union(
context.Categories.Select(c => new
{
Type = "Category",
ID = c.CategoryID,
Name = c.CategoryName,
SupplierName = (string)null
})
)
.ToList();
var viewModel = new ViewModel
{
Products = combinedResults
.Where(x => x.Type == "Product")
.Select(x => new ProductViewModel
{
ID = x.ID,
Name = x.Name,
SupplierName = x.SupplierName
}),
Categories = combinedResults
.Where(x => x.Type == "Category")
.Select(x => new CategoryViewModel
{
ID = x.ID,
Name = x.Name
})
};

Not able to access list of record in EF

Here is the problem I am trying to access some records from the data base based on one field . The field I am using is audit_id having type GUID .
but the line does not returning any data
var audits = ctx.Audits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
Here is a my full code to update mass records in the database using EF
//will select auditId from the List
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x=>x.AuditId).ToList();
using (var ctx = new AuditEntities())
{
var audits = ctx.Audits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
audits.ForEach(x => x.working_list = false);
ctx.SaveChanges();
}
In case of single record it return data from database
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x => x.AuditId).ToList();
Guid tempAuditId = lstAudits[0];
// lstAudits.ForEach(x => x.ToString().ToUpper());
using (var ctx = new AuditEntities())
{
var audits = (from au in ctx.Audits
where au.audit_id == tempAuditId
select au).ToList();
//foreach(Audit audit in audits){
//}
audits[0].working_list = false;
ctx.SaveChanges();
}
Finally I got the answer here is the updated code which is working fine .I just took some intermediate result in a temporary variable and it started working as expected
//will select auditId from the List
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x => x.AuditId).ToList();
using (var ctx = new AuditEntities())
{
var tempAudits = ctx.Audits.ToList();
var audits = tempAudits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
audits.ForEach(x => x.working_list = false);
ctx.SaveChanges();
}