Entity Framework Eager Loading not working - c#

I want to load a list of objects from db using Entity Framework 6 and Eager loading. But Entity Framework instead uses lazy loading. I've used SQL profiler and the queries are executed when a property referring to the child entities is accessed.
By using the 'Include' you suppose to load related entities in one go but it's not happening. My Code is presented below:
using (var flightsPricingRulesContext = new FlightsPricingRulesDbContext())
{
flightsPricingRulesContext.Configuration.ValidateOnSaveEnabled = false;
flightsPricingRulesContext.Configuration.AutoDetectChangesEnabled = false;
var filter = PredicateBuilder.True<FlightsPricingRulesDataAccess.Models.ServiceFee>();
if (!String.IsNullOrEmpty(selectedMarketId))
{
var selectedMarket = Int32.Parse(selectedMarketId);
filter = filter.And(sf => sf.MarketId == selectedMarket);
}
if (!String.IsNullOrEmpty(selectedApplicationTypeId))
{
var selectedAppplicationType = Int32.Parse(selectedApplicationTypeId);
filter = filter.And(sf => sf.ApplicationType == selectedAppplicationType);
}
var Query =
from P in flightsPricingRulesContext.ServiceFee.AsExpandable().Where(filter)select P;
switch (orderby)
{
case null:
case "":
case "Id":
Query = String.IsNullOrEmpty(orderbydirection) || orderbydirection == "ASC"
?Query.OrderBy(p => p.Id): Query.OrderByDescending(p => p.Id);
break;
case "market":
Query = String.IsNullOrEmpty(orderbydirection) || orderbydirection == "ASC"
? Query.OrderBy(p => p.MarketId)
: Query.OrderByDescending(p => p.MarketId);
break;
}
var takeitems = 10 ;
var skipitems = (Int32.Parse(page) - 1) * 10);
//BY USING INCLUDE EAGER LOADING IS ENABLED
Query = Query.Skip(skipitems).Take(takeitems).
Include(sf => sf.ServiceFeeZone.Select(sfz => sfz.Zone)).
Include(sf => sf.ServiceFeeCarrier).
Include(sf => sf.ServiceFeeClassOfService).
Include(sf => sf.ServiceFeeDate).
Include(sf => sf.ServiceFeeMarkUpAssignment).
Include(sf => sf.ServiceFeeAssignment);
var results = Query.ToList();//HERE A COMPLETE QUERY SHOULD BE
//SENT TO THE DB FOR RETRIEVING ENTITES INCLUDING THEIR CHILDREN
var totalresults = flightsPricingRulesContext.ServiceFee.AsExpandable().Count(filter);
var pagedservicefees = new PagedServiceFee();
pagedservicefees.totalitems = totalresults.ToString();
pagedservicefees.servicefees = new List<FlightsPricingRules.Models.ServiceFee>();
foreach (var servicefeedto in results)
{
var servicefee = new FlightsPricingRules.Models.ServiceFee();
servicefee.id = servicefeedto.Id.ToString();
servicefee.marketId = servicefeedto.MarketId.ToString();
//.....
//SOME MORE PROPERTIES
//
//CHILD ENTITIES
//Zones
servicefee.zones = new List<Zone>();
//HERE AN ADDITIONAL QUERY IS MADE TO LOAD THE CHILD ENTITIES-WHY?
foreach (var zonedto in servicefeedto.ServiceFeeZone)
{
var zone = new Zone();
zone.id = zonedto.ZoneId.ToString();
zone.name = zonedto.Zone.Name;
servicefee.zones.Add(zone);
}
//Carriers
servicefee.carriers = new List<Carr>();
//ALSO HERE AND ADDITIONAL QUERY IS MADE
foreach (var carrierdto in servicefeedto.ServiceFeeCarrier)
{
var carrier = new Carr();
carrier.id = carrierdto.AirlineId.ToString();
servicefee.carriers.Add(carrier);
}
pagedservicefees.servicefees.Add(servicefee);
}
//.......
//SOME MORE CHILD ENTITIES
//
return Json(pagedservicefees, JsonRequestBehavior.DenyGet);
}

OK, I figured it out. It turns out that it does matter where you place the Include statements. I placed the Include statements before the .AsExpandable() and after the parent entity and now eager loading is performed. I can also verify with SQL profiler, the query contains all the necessary joins and it's executed really fast. The correct query is now:
var Query = from P in flightsPricingRulesContext.ServiceFee
.Include(sf => sf.ServiceFeeCarrier)
.Include(sf=>sf.ServiceFeeAssignment)
.Include(sf => sf.ServiceFeeClassOfService)
.Include(sf => sf.ServiceFeeDate)
.Include(sf => sf.ServiceFeeMarkUpAssignment)
.Include(sf => sf.ServiceFeeZone.Select(zo => zo.Zone))
.AsExpandable().Where(filter) select P;
Posting the answer in case someone encounters a same scenario.

Related

Reusing queries with Entity Framework Core

I'm trying to make some queries using EF-Core and I have the following code
public List<Visitation> GetAllVisitations()
{
return this.hospital.Visitations
.Where(v => v.DoctorId == this.Doctor.Id)
.Select(v => new Visitation
{
Doctor = v.Doctor,
Patient = v.Patient,
Date = v.Date,
Comments = v.Comments
})
.ToList();
}
public List<Visitation> GetVisitationByPatient(int id)
{
var patient = this.GetPatientById(id);
return this.hospital.Visitations
.Where(v => v.PatientId == patient.Id)
.Select(v => new Visitation
{
Doctor = v.Doctor,
Patient = v.Patient,
Date = v.Date,
Comments = v.Comments
})
.ToList();
}
It is pretty obvious that the Select statement is the same in both methods. However I know that EF Core uses Expression<Func>, rather than Func therefore I do not know how to make an Expression, which can be used in both Select statements.
The query won't execute until you call .ToList(). So you may take the partial query up to the .Where() and pass it to a function that adds the Select() portion.
Something like this:
public List<Visitation> GetAllVisitations()
{
var query = this.hospital.Visitations
.Where(v => v.DoctorId == this.Doctor.Id);
return this.addTransformation(query)
.ToList();
}
public List<Visitation> GetVisitationByPatient(int id)
{
var patient = this.GetPatientById(id);
var query = this.hospital.Visitations
.Where(v => v.PatientId == patient.Id)
return this.addTransformation(query)
.ToList();
}
public IQueriable<Visitation> AddTransformation(IQueriable<Visitation> query)
{
return query.Select(v => new Visitation
{
Doctor = v.Doctor,
Patient = v.Patient,
Date = v.Date,
Comments = v.Comments
});
}

Change orderby depending on value of var

I have a foreach with an ordebydescending(), but in one case I need to use an orderby() instead. Depending on the value of articleType how can I use an inline condition inside the foreach to allow this to happen.
This is the condition I need to build into to determine the use of orderbydescending or orderby
if (articleType == BusinessLogic.ArticleType.Webinar)
This is the full function
public static List<Article> GetArticles(BusinessLogic.ArticleType articleType, long languageID)
{
List<Article> articles = new List<Article>();
using (var db = new DatabaseConnection())
{
foreach (var record in db
.GetArticles(BusinessLogic.SystemComponentID, (int) articleType, languageID)
.OrderByDescending(c => c.Date_Time))
{
#region articleTextLanguageID
long articleTextLanguageID = GetArticleTextLanguageID(record.ID, languageID);
string previewImageName = GetArticle(record.ID, articleTextLanguageID).PreviewImageName;
#endregion
Article article = new Article()
{
ID = record.ID,
Title = record.Title,
Summary = record.Summary,
PreviewImageName = previewImageName,
Date = record.Date_Time,
ArticleTextLanguageID = articleTextLanguageID
};
articles.Add(article);
}
}
return articles;
}
Was thinking something along these lines, but its not working
foreach (var record in db
.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID)
.Where(articleType == BusinessLogic.ArticleType.Webinar.ToString()?.OrderByDescending(c => c.Date_Time)) : .OrderBy(c => c.Date_Time)))
The way to do this would be to construct the query in pieces. For example:
var query = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = query.OrderByDescending(c => c.Date_Time);
}
else
{
query = query.OrderBy(c => c.Date_Time);
}
foreach(var record in query)
{
// process
}
If you required additional sorting, you'd need an extra variable typed as IOrderedEnumerable/IOrderedQueryable (depending on what GetArticles returns) as an intermediate to chain ThenBy/ThemByDescending:
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
IOrderedEnumerable<Article> query;
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = source.OrderByDescending(c => c.Date_Time);
}
else
{
query = source.OrderBy(c => c.Date_Time);
}
if(somethingElse)
{
query = query.ThenBy(c => c.OtherProperty);
}
foreach(var record in query)
{
// process
}
Based on your comment below, as notes above the second example would look more like the following (this means that db.GetArticles returns an IQueryable<Article> and not an IEnumerable<Article>):
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
IOrderedQueryable<Article> query;
if(articleType == BusinessLogic.ArticleType.Webinar)
{
query = source.OrderByDescending(c => c.Date_Time);
}
else
{
query = source.OrderBy(c => c.Date_Time);
}
if(somethingElse)
query = query.ThenBy(c => c.OtherProperty);
foreach(var record in query)
{
// process
}
You could also shorten it to the following:
var source = db.GetArticles(BusinessLogic.SystemComponentID, (int)articleType, languageID);
var query = articleType == BusinessLogic.ArticleType.Webinar
? source.OrderByDescending(c => c.Date_Time)
: source.OrderBy(c => c.Date_Time);
if(somethingElse)
query = query.ThenBy(c => c.OtherProperty);
foreach(var record in query)
{
// process
}

Entity Framework: How to query a number of related tables in a database making a single trip

I have a query that is currently far too slow.
I am trying to search a Code (a string) on the main page that will bring the user the relevant info.
Eg. The user can search a code from the main page and this will search for the code in Job, Work Phase, Wbs, Work Element, EA, Jobcard and Estimate and return the relevant info.
I make a number of trips to the database to collect the data i need when I believe it can be done in just one.
I have a number of tables that are all linked:
Contracts, Jobs, WorkPhases, Wbss, Engineering Activities, Jobcards and Estimates.
Contracts have a list of Jobs,
Jobs have a list of Workphases,
Workphases have a list of Wbss etc
Is there a quicker way to do this?
public Result Handle(Query query)
{
query.Code = query.Code ?? string.Empty;
var result = new Result();
//result.SetParametersFromPagedQuery(query);
result.Items = new List<Item>();
if (query.SearchPerformed)
{
var contracts = _db.Contracts.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(contracts.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
Type = MainPageSearchEnum.Contract,
ContractName = x.Name,
Url = string.Format("Admin/Contract/Edit/{0}", x.Id)
})).ToList();
var jobs = _db.Jobs.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(jobs.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
ContractName = x.Contract.Name,
Type = MainPageSearchEnum.Job,
Url = string.Format("Admin/Job/Edit/{0}", x.Id)
})).ToList();
//var workPhases = _db.WorkPhases.AsEnumerable().Where(x => x.ContractPhase.Code.ToLower() == query.Code.ToLower());
var workPhases = _db.WorkPhases.AsEnumerable().Where(x => x.ContractPhase.Code == query.Code);
result.Items = result.Items.Concat(workPhases.Select(x => new Item()
{
Code = x.ContractPhase.Code,
Id = x.Id,
Name = x.ContractPhase.Name,
Type = MainPageSearchEnum.WorkPhase,
Url = string.Format("Admin/WorkPhase/Edit/{0}", x.Id)
})).ToList();
var wbss = _db.WBSs.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(wbss.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
Type = MainPageSearchEnum.WBS,
Url = string.Format("Admin/WBS/Edit/{0}", x.Id)
})).ToList();
var eas = _db.EngineeringActivities.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(eas.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
Type = MainPageSearchEnum.EA,
Url = string.Format("Admin/EngineeringActivity/Edit/{0}", x.Id)
})).ToList();
var jcs = _db.Jobcards.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(jcs.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
Type = MainPageSearchEnum.EA,
Url = string.Format("Admin/JobCard/Edit/{0}", x.Id)
})).ToList();
var estimates = _db.Estimates.AsEnumerable().Where(x => x.Code == query.Code);
result.Items = result.Items.Concat(estimates.Select(x => new Item()
{
Code = x.Code,
Id = x.Id,
Name = x.Name,
Type = MainPageSearchEnum.Estimate,
Url = string.Format("Estimation/Estimate/Edit/{0}", x.Id)
})).ToList();
}
return result;
}
Disclaimer: I'm the owner of the project Entity Framework Plus
This library has a Query Future feature which allows batching multiple queries in a single roundtrip.
Example:
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntitiesContext();
// CREATE a pending list of future queries
var futureCountries = ctx.Countries.Where(x => x.IsActive).Future();
var futureStates = ctx.States.Where(x => x.IsActive).Future();
// TRIGGER all pending queries in one database round trip
// SELECT * FROM Country WHERE IsActive = true;
// SELECT * FROM State WHERE IsActive = true
var countries = futureCountries.ToList();
// futureStates is already resolved and contains the result
var states = futureStates.ToList();
Wiki: EF+ Query Future
Have you tried the Union / UnionAll operator?
It's purpose is exactly like you wish - combine the identical data from different sources.
Furthermore due to the concept of deferred execution your query will only be executed when you actually iterate over the result (or call a method that does that, for example - .ToList()
var contractsQuery = _db.Contracts.AsEnumerable().Where(x => x.Code == query.Code).Select(x=>new {Code=x.Code, Id=x.Id, ...});
var jobsQuery = _db.Jobs.AsEnumerable().Where(x => x.Code == query.Code).Select(x=>new{Code=x.Code, Id=x.Id, ...});
var workPhasesQuery = _db.WorkPhases.AsEnumerable().Where(x => x.ContractPhase.Code == query.Code).Select(x=>new{Code=x.Code, Id=x.Id, ...});
// and so on
var combinedQuery = contractsQuery.UnionAll(jobsQuery).UnionAll(workPhasesQuery ).UnionAll(...
var result = combinedQuery.ToList();
A similar question is Union in linq entity framework
Another code sample can be found here
Please notice that this is exactly the same concept of manipulating data as in T-SQL union, and under the covers you will get an sql query using a union operator
Yes there most certainly is a way to query multiple tables. You can use the Include() method extension for your query. for instance:
var examplelist = _db.Contracts.(v => v.id == "someid" && v.name == "anotherfilter").Include("theOtherTablesName").ToList();
You can include as many tables as you like this way. This is the recommended method.
You can also use the UnionAll() method but you'd have to define your queries separately for this

C# Linq query to return Dictionary<int,int[]>

Ok, so I have a need to create/return a Dictionary from the results of a Linq Query. I have tried just about everything I can think of and keep running into issues. Here is what I am currently attempting...
public static Dictionary<int,int[]> GetEntityAuthorizations(int userId)
{
using (MyDb db = new MyDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityId = query.Select(x => x.EntityManager.EntityId);
var roles = query.Select(x => x.RoleId).ToArray();
var result = query.ToDictionary(entityId, roles);
return result;
}
}
any help at all would be greatly appreciated. what i am looking for to be returned from this is a Dictionary where the Key is the entityId or EntityManager.EntityId and the Value(s) are an array of associated RoleId's.
Currently I am getting the following two errors at compile time, and other attempts have been errors similar but not exact to these.
Error 11 The type arguments for method 'System.Linq.Enumerable.ToDictionary<TSource,TKey>(System.Collections.Generic.IEnumerable, System.Func<TSource,TKey>, System.Collections.Generic.IEqualityComparer<TKey>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Error 12 Cannot implicitly convert type 'System.Collections.Generic.Dictionary<TKey,Sqor.Database.DbEntityManagerRoleAssignment>' to 'System.Collections.Generic.Dictionary<int,int[]>'
UPDATE - Working Solution (Thanks to #D Stanley)
public static Dictionary<int,int[]> GetEntityAuthorizations(int userId)
{
using (SqorDb db = new SqorDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityGroups = query.GroupBy(x => x.EntityManager.EntityId);
var result = entityGroups.ToDictionary(e => e.Key,
g => g.Select(x => x.RoleId).ToArray()
);
return result;
}
}
It sounds like you want to group by the Entity ID and project the associated role IDs to an array:
using (MyDb db = new MyDb())
{
var query = db.EntityManagerRoleAssignments.Where(x => x.EntityManager.ManagerId == userId);
var entityGroups = query.GroupBy(x => x.EntityManager.EntityId);
var result = entityGroups.ToDictionary(e => e.Key,
g => g.Select(x => x.RoleId).ToArray()
);
return result;
}

EF7 - How to get values from DbSet before change

I try to get the value of entity that stored in DbSet before it was changed by code and before it was saved. However, when I try to get it with LINQ Single statement I get the changed value. I'm using EF7.
Here's the code:
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
ent.FirstName = "New name";
Entity entityBeforeChange = dbSet.Single(x => x.Id == id); //here I want to get entity with old values, if that's important I just need to read it without modifying this instance
Context.SaveChanges();
Hope I was clear enough and can get some help
You can grab the original values of any entity from the ChangeTracker.
var original = Context.ChangeTracker.Entries<Entity>().Single(x => x.Id == id);
var firstName = original.Property<string>("FirstName").OriginalValue;
Here is a code I use from my audit library.
EF7
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
context.ChangeTracker.DetectChanges();
// Find your entry or get all changed entries
var changes = context.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified);
foreach (var objectStateEntry in changes)
{
AuditEntityModified(audit, objectStateEntry, auditState);
}
}
public static void AuditEntityModified(Audit audit, AuditEntry entry, EntityEntry objectStateEntry)
{
foreach (var propertyEntry in objectStateEntry.Metadata.GetProperties())
{
var property = objectStateEntry.Property(propertyEntry.Name);
if (entry.Parent.CurrentOrDefaultConfiguration.IsAudited(entry.ObjectStateEntry, propertyEntry.Name))
{
entry.Properties.Add(new AuditEntryProperty(entry, propertyEntry.Name, property.OriginalValue, property.CurrentValue));
}
}
}
EF6
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
var entry = ((IObjectContextAdapter)ctx).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
var currentValues = entry.CurrentValues;
var originalValues = entry.OriginalValues;
AuditEntityModified(originalValues, currentValues);
}
public static void AuditEntityModified(DbDataRecord orginalRecord, DbUpdatableDataRecord currentRecord, string prefix = "")
{
for (var i = 0; i < orginalRecord.FieldCount; i++)
{
var name = orginalRecord.GetName(i);
var originalValue = orginalRecord.GetValue(i);
var currentValue = currentRecord.GetValue(i);
var valueRecord = originalValue as DbDataRecord;
if (valueRecord != null)
{
// Complex Type
AuditEntityModified(valueRecord, currentValue as DbUpdatableDataRecord, string.Concat(prefix, name, "."));
}
else
{
if (!Equals(currentValue, originalValue))
{
// Add modified values
}
}
}
}
Edit:
The complete source code can be found in my GitHub repository: https://github.com/zzzprojects/EntityFramework-Plus
Library Website: http://entityframework-plus.net/
You can Detach an entity from the context. Keep in mind that you'll have to pull it from the context before you update the other, attached entity.
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
Entity entityBeforeChange = dbSet.Single(x => x.Id == id);
Context.Entry(entityBeforeChange).State = EntityState.Detached; // severs the connection to the Context
ent.FirstName = "New name";
Context.SaveChanges();
You could use a new DbContext since the loaded entity is cached in the one you already have.
Entity oldUnchanged;
using (var ctx = new YourDbContext())
{
oldUnchanged = ctx.Set<Entity>().Single(x => x.Id == id);
}

Categories