Counting performance issue - performance

The class Group is like the following:
class Group {
public string Name { get; set; }
public virtual ICollection<Person> Members { get; set; }
public dynamic AsJson() {
return new
{
groupId = this.GroupId,
name = this.Name,
membersCount = this.Members.Count // issue!
};
}
}
I have a view where all groups are listed, like follows:
Group name Members
Bleh 15
Zort 40
Narf 12
This list is retrieved with an AJAX call which returns a JSON. The action has code like the following:
groups = from g in (db.Groups where ... orderby g.Name).ToList()
select g.AsJson();
The problem is that this list was taking way too long (like 20 seconds).
I changed "this.Members.Count" with a maintained property, that is:
class Group {
public string Name { get; set; }
public virtual ICollection<Person> Members { get; set; }
public int MembersCount { get; set; } // added
public dynamic AsJson() {
return new
{
groupId = this.GroupId,
name = this.Name,
membersCount = this.MembersCount // changed
};
}
}
And it started to work fine (1-2 secs to generate the list)
Is there a better way to achieve this? I'm starting to have problems maintaining the property MembersCount, because Members are added and removed in several parts of the code

Related

Why is adding objects to my model so slow?

I have a SimID query that joins a given list of cell phone numbers with their respective IDs in my DB, if the number doesn't exist in the DB it returns 0 as an ID. If I watch my query while debugging, it executes perfectly and very quickly. However when I loop through the results of the query and create a new object and add it to my model it takes 3 minutes to create 458 new objects.
I am new to EF and Linq what am I doing wrong? How can I optimize this code to execute faster?
Any help would be greatly appreciated.
//Query to create virtual table of SimID and MSISDN joined on Cache.MSISDN
var SimID = (from ca_msisdn in Cache.MSISDN
join db_simobj in ctx.Sims on ca_msisdn equals db_simobj.Msisdn into Holder
from msisdnresult in Holder.DefaultIfEmpty()
select new { MSISDN = ca_msisdn, ID = (msisdnresult == null || msisdnresult.SimId == 0 ? 0 : msisdnresult.SimId) });
//Loop through virtual tables and add new data to model
foreach (var ToUpdate in SimID)
{
if (ToUpdate.ID == 0)
{
Console.WriteLine("We have found a new MSISDN: " + ToUpdate.MSISDN + " adding it to the model.");
ctx.Sims.Add(new Sim { Msisdn = ToUpdate.MSISDN });
}
}
//My Sim object
public partial class Sim
{
public Sim()
{
this.CDR_Event = new HashSet<CDR_Event>();
}
public long SimId { get; set; }
public long SimStatusId { get; set; }
public Nullable<long> FitmentCentreId { get; set; }
public string Serial { get; set; }
public string Msisdn { get; set; }
public string Puk { get; set; }
public string Network { get; set; }
public Nullable<System.DateTime> SimStatusDate { get; set; }
public Nullable<System.DateTime> ActivationDate { get; set; }
public Nullable<System.DateTime> ExpiryDate { get; set; }
public Nullable<long> APNSimStatusId { get; set; }
public Nullable<System.DateTime> APNActivated { get; set; }
public Nullable<System.DateTime> APNConfirmed { get; set; }
public string Svr { get; set; }
public virtual ICollection<CDR_Event> CDR_Event { get; set; }
}
Your main problem is that every time that you add to the Sims collection on your context you are adding to what the context's change tracker needs to keep track of, which can become a very expensive proposition memory-wise when you start having larger amounts of entities.
You can solve this in one of two ways:
1) Save the data in batches. That way the change tracker never has to keep track of a large amount of added entities.
2) If you don't want to save it right away, keep it as an in-memory list first, and then do #1 when you do go to save those records.
The answer to this question as IronMan84 alluded to; as documented here is to use .AddRange() and add everything all at once as a list outside the foreach instead of one by one inside the loop.
List<Sim> _SIMS = new List<Sim>();
foreach (var ToUpdate in SimID)
{
if (ToUpdate.ID == 0)
{
Console.WriteLine("We have found a new MSISDN: " + ToUpdate.MSISDN + " adding it to the model.");
_SIMS.Add(new Sim { Msisdn = ToUpdate.MSISDN} );
}
}
ctx.Sims.AddRange(_SIMS);

Retrieving information from derived child object collections using LINQ

I have been trying to get a list of all Workflows that have Offices contained in a certain List by Office Id. I can easily get all of the Workflows that have SingleWorkflowSteps because they have only one Office, but have been unable to understand how I would successfully get those contained in a MultiWorkflowStep. All workflow steps have either a SingleWorkflowStep or a MultiWorkflowStep that contains two or more SingleWorkflowSteps. At the time I designed this, it seemed like a logical way to do this but atlas my LINQ-fu is not as good as I thought it was. Can someone please point me in the right directions. Code listed below:
var OfficesToFind = new List<int> (new int[] { 1,3,5,7,9,10,11,12} );
public class Workflow
{
public Workflow()
{
WorkflowSteps = new List<WorkflowStepBase>();
}
public int Id { get; set; }
public virtual ICollection<WorkflowStepBase> WorkflowSteps { get; set; }
}
public abstract class WorkflowStepBase
{
public int Id { get; set; }
public int StatusId { get; set; }
public virtual Workflow Workflow { get; set; }
public virtual Status Status { get; set; }
}
public class MultiWorkflowStep : WorkflowStepBase
{
public MultiWorkflowStep()
{
ChildSteps = new List<SingleWorkflowStep>();
}
public virtual ICollection<SingleWorkflowStep> ChildSteps { get; set; }
}
public class SingleWorkflowStep : WorkflowStepBase
{
public int? ParentStepId { get; set; }
public int OfficeId { get; set; }
public virtual MultiWorkflowStep ParentStep { get; set; }
public virtual Office Office { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkflowService : IWorkflowService<Workflow>
{
private readonly IRepository<Workflow> _workflowService;
private readonly IRepository<SingleWorkflowStep> _singleStepService;
private readonly IRepository<MultiWorkflowStep> _multiStepService;
public WorkflowService(IUnitOfWork uow)
{
_workflowService = uow.GetRepository<Workflow>();
_singleStepService = uow.GetRepository<SingleWorkflowStep>();
_multiStepSercice = uow.GetRepository<MultiWorkflowStep>();
}
// ~ ------- Other CRUD methods here -------- ~
public IEnumerable<Workflow> GetWorkflowFilter(List<int> statuses, List<int> offices...)
{
var query = _workflowService.GetIQueryable(); // returns an IQueryable of dbset
if(statuses.Any())
{
query = query.Where(q => statuses.Contains(q.StatusId));
}
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService
.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE)
.Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}
}
OK, after a good night sleep, being all bright-eyed and bushy-tailed, I figured out my own problem. First the updated code was all wrong. Because each derived WorkflowStep has access to the Workflow and each MultiWorkflowStep contains a list of SingleWorkflowSteps - when I get the list of all SingleWorkflowSteps (which would include all from MultiWorkflowStep(s)), I simply needed to get a list of all of the parent Workflows of the SingleWorkflowSteps. Next I updated my query to limit the Workflows that were contained in the new Workflow list and here is the correct code for the GetWorkflowFilter method:
...
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE).Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}

Automapper unable to project one enum type to another

I'm leveraging the Project functionality in Automapper and Entity Framework, but I'm running into an issue where Automapper doesn't seem to want to project one enum type to another.
I have the following entities:
public class UserProfile
{
public Guid Id { get; set; }
public string Name { get; set; }
private HashSet<UserProfilePhone> m_Phones;
public virtual HashSet<UserProfilePhone> Phones
{
get { return m_Phones ?? (m_Phones = new HashSet<UserProfilePhone>()); }
set { this.m_Phones = value; }
}
}
public class UserProfilePhone
{
public PhoneType Type { get; set; }
public virtual string Number { get; set; }
}
public enum PhoneType
{
Home = 1,
Work = 2,
Mobile = 3,
Other = 4
}
I then am projecting these types to the following models:
public class UserProfileModel
{
public Guid Id { get; set; }
public virtual string Name { get; set; }
public IEnumerable<UserProfilePhoneModel> Phones { get; set; }
}
public class UserProfilePhoneModel
{
public UserProfilePhoneTypeModel Type { get; set; }
public string Number { get; set; }
}
public enum UserProfilePhoneTypeModel
{
Home = 1,
Work = 2,
Mobile = 3,
Other = 4
}
I then setup my mappings like so:
Mapper.CreateMap<PhoneType, UserProfilePhoneTypeModel>();
Mapper.CreateMap<UserProfilePhone, UserProfilePhoneModel>();
Mapper.CreateMap<UserProfile, UserProfileModel>();
And finally I'm executing my projection:
var result = dbContext.UserProfiles.Project().To<UserProfileModel>();
When I do this, I get the following exception:
AutoMapper.AutoMapperMappingException: Unable to create a map expression from MyNamespace.PhoneType to
MyNamespace.Models.UserProfilePhoneTypeModel
Unable to create a map expression from MyNamespace.PhoneType to MyNamespace.Models.UserProfilePhoneTypeModel
Result StackTrace:
at System.Collections.Concurrent.ConcurrentDictionary2.GetOrAdd(TKey
key, Func2 valueFactory)
...
I've tried creating explicit mappings, but they appear to be ignored. What am I doing wrong here?
As usual, I figured out the answer almost as soon as I posted the question.
Modifying the create map line to provide an explicit cast did the trick:
Mapper.CreateMap<UserProfilePhone, UserProfilePhoneModel>()
.ForMember(m => m.Type, opt => opt.MapFrom(t => (UserProfilePhoneTypeModel)t.Type));

Query that Groups Parent, then Child, when fetching from IQueryable<GrandChild>

Lets imagine that below is our data model. I want to query the Toys but have the results returned such that I can do the following psuedo code.
foreach (var parent in parents)
{
Console.WriteLine(parent.Name);
foreach (var child in parent.Children)
{
Console.WriteLine(child.Name);
foreach (var toy in child.Toys)
{
Console.WriteLine(toy.Name);
}
}
}
The data model:
public class Parent
{
public Guid ParentId { get; set; }
public String Name { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public Guid ChildId { get; set; }
public Guid ParentId { get; set; }
public Parent Parent { get; set; }
public String Name { get; set; }
public ICollection<Toy> Toys { get; set; }
}
public class Toy
{
public Guid ToyId { get; set; }
public Guid ChildId { get; set; }
public Child Child { get; set; }
public String Name { get; set; }
public bool IsCool { get; set; }
}
I've tried doing this with grouping but when I try to iterate a grouping all that I see is Key which doesn't have any properties on it so I can't get the Parent.Name or the Child.Name.
Thanks in advance.
You can do:
var query = from p in parents
from c in p.Children
from t in c.Toys
select new { Parent = p.Name, Child = c.Name, Toy = t.Name }
foreach (var a in query)
{
// write values.
}
In effect, this uses SelectMany.
Edit
After your comment, I think what you're looking for is not a grouping. If you've got a Parent object in fact its Children are already grouped inside of it, because each parent has its own collection. But maybe you're looking for a way to populate the collections? That would be done by
var query = from p in parents.Include(x => x.Children.Select(c => c.Toys));
With LazyLoadingEnabled set to be false, you will be able to do this by using one linq query to load all related objects.
Please note, this may cause some performance issue if you have great amount of related data in Children or Toys table.
context.ContextOptions.LazyLoadingEnabled = false;
var parents = context.Parents.Select(x=>x).ToList();
foreach(var parent in parents) {
Console.WriteLine(parent.Name);
foreach(var child in parent.Children) {
Console.WriteLine(child.Name);
foreach(var toy in child.Toys) {
Console.WriteLine(toy.Name);
}
}
}

How to Count items in nested collection / codefirst EntityFramework

I've got CodeFirst collection defined as defined below.
For any given EmailOwnerId, I want to count the number of EmailDetailAttachments records exist without actually downloading all the images themselves.
I know I can do something like
var emailsToView = (from data in db.EmailDetails.Include("EmailDetailAttachments")
where data.EmailAccount.EmailOwnerId = 999
select data).ToList();
int cnt = 0;
foreach (var email in emailsToView)
{
cnt += email.EmailDetailAttachments.Count();
}
but that means I've already downloaded all the bytes of images from my far away server.
Any suggestion would be appreciated.
public class EmailDetail
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int EmailOwnerId {get;set;}
public virtual ICollection<ImageDetail> EmailDetailAttachments { get; set; }
..
}
public class ImageDetail
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLengthAttribute(256)]
public string FileName { get; set; }
[MaxLengthAttribute(256)]
public string ContentMimeType { get; set; }
public byte[] ImageDataBytes { get; set; }
public DateTime ImageCreation { get; set; }
}
The engine should be able to update this to a COUNT(*) statement.
var emailsToView = (from data in db.EmailDetails // no Include
where data.EmailAccount.EmailOwnerId = 999
select new {
Detail = data,
Count=data.EmailDetailAttachments.Count() }
).ToList();
But you'll have to verify if this produces the right (and more efficient) SQL.

Resources