Entity Framework Timeout Error - c#

I have this method
public static List<SummaryItinerary> ReturnBookingsByUserGuid(Guid userGuid)
{
var entities = new gHOPEntities();
var results = from itinerary in entities.Itinerary
where itinerary.UserGuid == userGuid
where itinerary.Booking
select new SummaryItinerary()
{
TourTitle = itinerary.Tours.Title,
TourId = itinerary.Tours.TourId,
TourSEOName =
itinerary.Tours.SEOName,
DepartureDate =
itinerary.DepartureDate,
Passengers = itinerary.Passengers,
Nights = itinerary.Nights,
GrandTotal = itinerary.GrandTotal,
AmountPaid = itinerary.AmountPaid,
CreationDate =
itinerary.CreationDate
};
var summaryItineraryList = new List<SummaryItinerary>();
foreach(var summaryItinerary in results)
{
summaryItineraryList.Add(summaryItinerary);
}
return summaryItineraryList.OrderByDescending(i =>
i.CreationDate).ToList();
}
This method fails when I call it. A timeout error is returned. However, when I put a breakpoint at the for loop, it passes. Why is this happening?
Thanks,
Sachin

This is beacuse in loop:
foreach(var summaryItinerary in results)
on every element it looks into database. This is enumerable, so access is through each element, and every element iteration checks database. To avoid that do following:
var tmp = results.ToList();
foreach(var summaryItinerary in tmp)

Related

GROUP BY to List<> with Linq

I'm new to using Linq so I don't understand some things or its syntax. I want to group a list and then loop through it with foreach, like my logic below. Obviously my logic doesn't work.
My code:
var final = finalv.Union(finalc);
final = final.GroupBy(x => x.Clave);
foreach (var articulo in final)
{
Articulo articulo2 = new Articulo();
articulo2.ArtID = articulo.ArtID;
articulo2.Clave = articulo.Clave;
articulo2.ClaveAlterna = articulo.ClaveAlterna;
lista.Add(articulo2);
}
First, such usage is syntactically consistent with this overloaded method of GroupBy: GroupBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>), and it will return a IEnumerable<IGrouping<TKey,TSource>> variable.
That means, if you run final.GroupBy(x => x.Clave), let's assume he returns finalWithGrouped, then finalWithGrouped.Key is the key and finalWithGrouped.ToList() is a collection of all variables with the same key(at here, it is with the same Clave).
And for your code, try this:
var final = finalv.Union(finalc);
var finalWithGrouped = final.GroupBy(x => x.Clave);
foreach (var articulosWithSameClavePair in finalWithGrouped)
{
var clave = articulosWithSameClavePair.Key;
var articulos = articulosWithSameClavePair.ToList();
foreach(var articulo in articulos)
{
Articulo articulo2 = new Articulo();
articulo2.ArtID = articulo.ArtID;
articulo2.Clave = articulo.Clave;
articulo2.ClaveAlterna = articulo.ClaveAlterna;
lista.Add(articulo2);
}
}
I suggest you read some examples of using GroupBy.
When you group a list, it will return a key and groued list and you are trying reach a single property of a list.
When you group an data, you can convert it to dictionary, It is not nessesary but better way for me. You can try this code:
var final = finalv.Union(finalc);
final = final.GroupBy(x => x.Clave).ToDictionary(s=> s.Key, s=> s.ToList();
foreach (var articulo in final)
{
foreach (var articuloItem in articulo.value)
{
Articulo articulo2 = new Articulo();
articulo2.ArtID = articuloItem.ArtID;
articulo2.Clave = articuloItem.Clave;
articulo2.ClaveAlterna = articuloItem.ClaveAlterna;
lista.Add(articulo2);
}
}

C# Calculate field inside LINQ Query

I need some help to calculate a property inside my Linq query.
I know I need to use "let" somewhere, but I can't figure it out!
So, first I have this method to get my list from Database:
public BindingList<Builders> GetListBuilders()
{
BindingList<Builders> builderList = new BindingList<Builders>();
var ctx = new IWMJEntities();
var query = (from l in ctx.tblBuilders
select new Builders
{
ID = l.BuilderID,
Projeto = l.NomeProjeto,
Status = l.Status,
DataPedido = l.DataPedido,
DataPendente = l.DataPendente,
DataEntregue = l.DataEntregue,
DataAnulado = l.DataAnulado
});
foreach (var list in query)
builderList.Add(list);
return builderList;
}
Then, I have a function to calculate the Days between Dates accordingly to Status:
public int GetDays()
{
int Dias = 0;
foreach (var record in GetListBuilders)
{
if (record.Status == "Recebido")
{
Dias = GetBusinessDays(record.DataPedido, DateTime.Now);
}
else if (record.Status == "Pendente")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataPendente);
}
else if (record.Status == "Entregue")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataEntregue);
}
else if (record.Status == "Anulado")
{
Dias = GetBusinessDays(record.DataPedido, (DateTime)record.DataAnulado);
}
}
return Dias;
}
I need to call the GetDays in a DataGridView to give the days for each record.
My big problem is, How do I get this? include it in Linq Query? Calling GetDays() (need to pass the ID from each record to GetDays() function)!?
Any help?
Thanks
I think it would be easier to create an extension method:
public static int GetBusinessDays(this Builders builder) // or type of ctx.tblBuilders if not the same
{
if (builder == null) return 0;
switch(builder.status)
{
case "Recebido": return GetBusinessDays(builder.DataPedido, DateTime.Now);
case "Pendente": return GetBusinessDays(builder.DataPedido, (DateTime)builder.DataPendente);
case "Entregue": return GetBusinessDays(builder.DataPedido, (DateTime)builder.DataEntregue);
case "Anulado": GetBusinessDays(builder.DataPedido, (DateTime)builder.DataAnulado);
default: return 0;
}
}
Then, call it like that:
public BindingList<Builders> GetListBuilders()
{
BindingList<Builders> builderList = new BindingList<Builders>();
var ctx = new IWMJEntities();
var query = (from l in ctx.tblBuilders
select new Builders
{
ID = l.BuilderID,
Projeto = l.NomeProjeto,
Status = l.Status,
DataPedido = l.DataPedido,
DataPendente = l.DataPendente,
DataEntregue = l.DataEntregue,
DataAnulado = l.DataAnulado,
Dias = l.GetBusinessDays()
});
foreach (var list in query)
builderList.Add(list);
return builderList;
}
To do better, to convert a object to a new one, you should create a mapper.
Why does it need to be a part of the query? You can't execute C# code on the database. If you want the calculation to be done at the DB you could create a view.
You're query is executed as soon as the IQueryable is enumerated at the foreach loop. Why not just perform the calculation on each item as they are enumerated and set the property when you are adding each item to the list?

How can I ensure rows are not loaded twice with EF / LINQ

I created code to load definitions from an external API. The code iterates through a list of words, looks up a definition for each and then I thought to use EF to insert these into my SQL Server database.
However if I run this twice it will load the same definitions the second time. Is there a way that I could make it so that EF does not add the row if it already exists?
public IHttpActionResult LoadDefinitions()
{
var words = db.Words
.AsNoTracking()
.ToList();
foreach (var word in words)
{
HttpResponse<string> response = Unirest.get("https://wordsapiv1.p.mashape.com/words/" + word)
.header("X-Mashape-Key", "xxxx")
.header("Accept", "application/json")
.asJson<string>();
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>(response.Body);
var results = rootObject.results;
foreach (var result in results)
{
var definition = new WordDefinition()
{
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
db.SaveChanges();
}
return Ok();
}
Also would appreciate if anyone has any suggestions as to how I could better implement this loading.
foreach (var result in results)
{
if(!(from d in db.WordDefinitions where d.Definition == result.definition select d).Any())
{
var definition = new WordDefinition()
{
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
}
You can search for Definition value.
var wd = db.WordDefinition.FirstOrDefault(x => x.Definition == result.definition);
if(wd == null) {
var definition = new WordDefinition() {
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
In this way you can get a WordDefinition that already have your value.
If you can also use WordId in the same way:
var wd = db.WordDefinition.FirstOrDefault(x => x.WordId == word.WordId);

Retrieve single record to model with EF Linq, does it need to loop to populate?

Most of the time I retrieve multiple records so I would end up doing this
var rpmuser = new List<rpm_scrty_rpm_usr>();
I have my List collection of properties from poco
So I typically use select new in my Linq statement
Then I use a foreach and loop over the records in which the List would get model.Add(new instance in each loop)
However , do I really need to be doing all this looping to populate?
Bigger question when i have a single record should I be needing to even do a loop at all?
public bool UpdateAllUsers(string user, string hash, string salt)
{
bool status = false;
var rpmuser = new rpm_scrty_rpm_usr();
var query = (from t in db.rpm_usr
.Where(z => z.usr_id == "MillXZ")
select new
{
t.usr_id,
t.usr_lnm,
t.usr_pwd,
t.usr_fnm,
t.salt,
t.inact_ind,
t.lst_accs_dtm,
t.lst_pwd_chg_dtm,
t.tel,
t.wwid,
t.email_id,
t.dflt_ste_id,
t.apprvr_wwid,
t.chg_dtm,
t.chg_usr_id,
t.cre_dtm,
t.cre_usr_id,
});
foreach(var s in query)
{
rpmuser.wwid = s.wwid;
rpmuser.usr_pwd = s.usr_pwd;
rpmuser.usr_lnm = s.usr_lnm;
rpmuser.usr_id = s.usr_id;
rpmuser.usr_fnm = s.usr_fnm;
rpmuser.tel = s.tel;
rpmuser.salt = s.salt;
rpmuser.lst_pwd_chg_dtm = rpmuser.lst_pwd_chg_dtm;
rpmuser.lst_accs_dtm = s.lst_accs_dtm;
rpmuser.inact_ind = s.inact_ind;
rpmuser.email_id = s.email_id;
rpmuser.apprvr_wwid = s.apprvr_wwid;
rpmuser.chg_dtm = s.chg_dtm;
rpmuser.chg_usr_id = s.chg_usr_id;
rpmuser.cre_usr_id = s.cre_usr_id;
rpmuser.dflt_ste_id = s.dflt_ste_id;
rpmuser.cre_dtm = s.cre_dtm;
}
DateTime dateTime = DateTime.Now;
try
{
rpmuser = db.rpm_usr.Find(rpmuser.usr_id);
rpmuser.usr_pwd = hash;
rpmuser.salt = salt;
db.SaveChanges();
status = true;
}
catch (Exception ex)
{
status = false;
}
return status;
}
I am not exactly sure what you want. Your method says Update All, but only seems to be attempting to update one record. So why don't you just do this?
try
{
var rpmuser = db.rpm_usr.Single(z => z.usr_id == "MillXZ");
rpmuser.usr_pwd = hash;
rpmuser.salt = salt;
db.SaveChanges();
status = true;
}
catch (Exception ex)
{
status = false;
}
You have a lot of redundant declarations unless I am missing something. In the case of the list you will do something like this:
var query = db.rpm_usr.Where(z => z.usr_id == "...some string...");
foreach(var item in query)
{
rpmuser.usr_pwd = ...some value...;
rpmuser.salt = ...some value...;
}
db.SaveChanges();
I can't stress this enough, Murdock's answer is absolutely the right way to fix the code you've shown. You are writing way too much code for what you're trying to accomplish.
However, to answer your question about whether you need to loop in other situations, you can get away from having to loop by doing the projection into a new type as part of your LINQ-to-Entities query. The looping still happens, you just don't see it.
var query = db.rpm_usr
.Where(z => z.usr_id == "MillXZ")
.AsEnumerable()
.Select(z => new rpm_scrty_rpm_usr()
{
usr_id = z.usr_id,
usr_lnm = z.usr_lnm,
// etc...
});
You would then finish the query off with a .Single(), .SingleOrDefault(), or .ToList() depending on whether you expected exactly one, one or zero, or a list. For example, in this case if you might find one or zero users with the name "MillXZ" you would write the following.
var query = db.rpm_usr
.Where(z => z.usr_id == "MillXZ")
.AsEnumerable()
.Select(z => new rpm_scrty_rpm_usr()
{
usr_id = z.usr_id,
usr_lnm = z.usr_lnm,
// etc...
})
.SingleOrDefault();

Very slow runtime with Entity Framework nested loop (using nav properties)

Right now, I'm trying to write a method for a survey submission program that utilizes a very normalized schema.
I have a method that is meant to generate a survey for a team of people, linking several different EF models together in the process. However, this method runs EXTREMELY slowly for anything but the smallest team sizes (taking 11.2 seconds to execute for a 4-person team, and whopping 103.9 seconds for an 8 person team). After some analysis, I found that 75% of the runtime is taken up in the following block of code:
var TeamMembers = db.TeamMembers.Where(m => m.TeamID == TeamID && m.OnTeam).ToList();
foreach (TeamMember TeamMember in TeamMembers)
{
Employee employee = db.Employees.Find(TeamMember.EmployeeID);
SurveyForm form = new SurveyForm();
form.Submitter = employee;
form.State = "Not Submitted";
form.SurveyGroupID = surveygroup.SurveyGroupID;
db.SurveyForms.Add(form);
db.SaveChanges();
foreach (TeamMember peer in TeamMembers)
{
foreach (SurveySectionDetail SectionDetail in sectionDetails)
{
foreach (SurveyAttributeDetail AttributeDetail in attributeDetails.Where(a => a.SectionDetail.SurveySectionDetailID == SectionDetail.SurveySectionDetailID) )
{
SurveyAnswer answer = new SurveyAnswer();
answer.Reviewee = peer;
answer.SurveyFormID = form.SurveyFormID;
answer.Detail = AttributeDetail;
answer.SectionDetail = SectionDetail;
db.SurveyAnswers.Add(answer);
db.SaveChanges();
}
}
}
}
I'm really at a loss as to how I might go about cutting back the runtime. Is this just the price I pay for having this many related entities? I know that joins are expensive operations, and that I've essentially got 3 Or is there some inefficiency that I'm overlooking?
Thanks for your help!
EDIT: As requested by Xiaoy312, here's how sectionDetails and attributeDetails are defined:
SurveyTemplate template = db.SurveyTemplates.Find(SurveyTemplateID);
List<SurveySectionDetail> sectionDetails = new List<SurveySectionDetail>();
List<SurveyAttributeDetail> attributeDetails = new List<SurveyAttributeDetail>();
foreach (SurveyTemplateSection section in template.SurveyTemplateSections)
{
SurveySectionDetail SectionDetail = new SurveySectionDetail();
SectionDetail.SectionName = section.SectionName;
SectionDetail.SectionOrder = section.SectionOrder;
SectionDetail.Description = section.Description;
SectionDetail.SurveyGroupID = surveygroup.SurveyGroupID;
db.SurveySectionDetails.Add(SectionDetail);
sectionDetails.Add(SectionDetail);
db.SaveChanges();
foreach (SurveyTemplateAttribute attribute in section.SurveyTemplateAttributes)
{
SurveyAttributeDetail AttributeDetail = new SurveyAttributeDetail();
AttributeDetail.AttributeName = attribute.AttributeName;
AttributeDetail.AttributeScale = attribute.AttributeScale;
AttributeDetail.AttributeType = attribute.AttributeType;
AttributeDetail.AttributeOrder = attribute.AttributeOrder;
AttributeDetail.SectionDetail = SectionDetail;
db.SurveyAttributeDetails.Add(AttributeDetail);
attributeDetails.Add(AttributeDetail);
db.SaveChanges();
}
}
There is several points that you can improve :
Do not SaveChanges() on each Add() :
foreach (TeamMember TeamMember in TeamMembers)
{
...
// db.SaveChanges();
foreach (TeamMember peer in TeamMembers)
{
foreach (SurveySectionDetail SectionDetail in sectionDetails)
{
foreach (SurveyAttributeDetail AttributeDetail in attributeDetails.Where(a => a.SectionDetail.SurveySectionDetailID == SectionDetail.SurveySectionDetailID) )
{
...
// db.SaveChanges();
}
}
}
db.SaveChanges();
}
Consider to reduce the numbers of round trips to the database. This can be done by : they are memory-intensive
using Include() to preload your navigation properties; or
cashing the partial or whole table with ToDictionary() or ToLookup()
Instead of Add(), use AddRange() or even BulkInsert() from EntityFramework.BulkInsert if that fits your setup :
db.SurveyAnswers.AddRange(
TeamMembers.SelectMany(p =>
sectionDetails.SelectMany(s =>
attributeDetails.Where(a => a.SectionDetail.SurveySectionDetailID == s.SurveySectionDetailID)
.Select(a => new SurveyAnswer()
{
Reviewee = p,
SurveyFormID = form.SurveyFormID,
Detail = a,
SectionDetail = s,
}))));
Use Include to avoid SELECT N + 1 issue.
SurveyTemplate template = db.SurveyTemplates.Include("SurveyTemplateSections")
.Include("SurveyTemplateSections.SurveyTemplateAttributes")
.First(x=> x.SurveyTemplateID == SurveyTemplateID);
Generate the whole object graph and then save to DB.
List<SurveySectionDetail> sectionDetails = new List<SurveySectionDetail>();
List<SurveyAttributeDetail> attributeDetails = new List<SurveyAttributeDetail>();
foreach (SurveyTemplateSection section in template.SurveyTemplateSections)
{
SurveySectionDetail SectionDetail = new SurveySectionDetail();
//Some code
sectionDetails.Add(SectionDetail);
foreach (SurveyTemplateAttribute attribute in section.SurveyTemplateAttributes)
{
SurveyAttributeDetail AttributeDetail = new SurveyAttributeDetail();
//some code
attributeDetails.Add(AttributeDetail);
}
}
db.SurveySectionDetails.AddRange(sectionDetails);
db.SurveyAttributeDetails.AddRange(attributeDetails);
db.SaveChanges();
Load all employees you want before the loop, this will avoids database query for every team member.
var teamMemberIds = db.TeamMembers.Where(m => m.TeamID == TeamID && m.OnTeam)
.Select(x=>x.TeamMemberId).ToList();
var employees = db.Employees.Where(x => teamMemberIds.Contains(x.EmployeeId));
create a dictionary for attributeDetails based on their sectionDetailId to avoid query the list on every iteration.
var attributeDetailsGroupBySection = attributeDetails.GroupBy(x => x.SectionDetailId)
.ToDictionary(x => x.Key, x => x);
Move saving of SurveyAnswers and SurveyForms to outside of the loops:
List<SurveyForm> forms = new List<SurveyForm>();
List<SurveyAnswer> answers = new List<SurveyAnswer>();
foreach (int teamMemberId in teamMemberIds)
{
var employee = employees.First(x => x.Id == teamMemberId);
SurveyForm form = new SurveyForm();
//some code
forms.Add(form);
foreach (int peer in teamMemberIds)
{
foreach (SurveySectionDetail SectionDetail in sectionDetails)
{
foreach (SurveyAttributeDetail AttributeDetail in
attributeDetailsGroupBySection[SectionDetail.Id])
{
SurveyAnswer answer = new SurveyAnswer();
//some code
answers.Add(answer);
}
}
}
}
db.SurveyAnswers.AddRange(answers);
db.SurveyForms.AddRange(forms);
db.SaveChanges();
Finally if you want faster insertions you can use EntityFramework.BulkInsert. With this extension, you can save the data like this:
db.BulkInsert(answers);
db.BulkInsert(forms);

Categories

Resources