Retrieve linked entities with LINQ - linq

I have the following EF Code First classes:
public class Request
{
[Key]
public virtual int RequestID { get; set; }
...
public virtual DateTime CreationDate { get; set; }
}
public class RequestLinked
{
[Key, Column(Order = 0)]
[ForeignKey("Request")]
public int RequestID { get; set; }
[Key, Column(Order = 1)]
[ForeignKey("RequestRelated")]
public int RequestRelatedID { get; set; }
public virtual Request Request { get; set; }
public virtual Request RequestRelated { get; set; }
}
I need to establish links between multiple requests.
For example: let's say I have Request IDs 1, 2, 3. Next I define links between 1-2 and 1-3 like below:
new RequestLinked { RequestID = 1, RequestRelatedID = 2 } <-- A
new RequestLinked { RequestID = 1, RequestRelatedID = 3 } <-- B
Based on the entities above, I would like to be able to see related requests for a specific request.
So for example:
for request 1: linked to 2 & 3
for request 2: linked to 1 & 3
for request 3: linked to 1 & 2
Any Idea how to get these links with LINQ ?
UPDATE
I'll try to be clear below:
Let's say we search linked requests for ID 3. What must be retrieved?
We found that ID 3 is linked to ID 1 (see entry A)
(Now we need to search if ID 1 has other linked elements)
And we found ID 1 is linked to ID 2 (see entry B)
==> Return 1 & 2

Related

Entity Framework very slow performance when get the navigation property

There are just two simplest table A and B where A contains many B as below table. Then I create 20000 record of B and add them to one A. Now, if I restart the program and execute the if(a.Bs==null) code line as below, it will cost 120 seconds!!
I don't think 20000 records is a big thing to EF, so is there anyone who can help me about this performance issue?
public class A
{
[Key]
public int EntityId { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
[Key]
public int EntityId { get; set; }
public virtual A A { get; set; }
}
////////////The Context is://///////////////
public class MyDbContext : DbContext
{
public MyDbContext()
: base("MyDbContext")
{
}
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
}
///////////Performance issue as below//////////////
using (var db = new MyDbContext())
{
var a = db.As.First();
if (a.Bs == null)\\This line will cost about 120 seconds!!!!!!
{}
}
In your code, when you access a.Bs, all the collection is materialized (by lazy loader) and 20.000 records materialization takes a lot of time. Also, I think that a.Bs is never null.
If you don't need Bs loaded in memory you can find another way to make the check, i.e.
if (a.Bs.Count() == 0) {}

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);

Counting performance issue

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

Complex LINQ or EF query

I have the following 3 classes.
A workflow configuration has one brand and one workflowtype.
I need one method in linq or EF that gets me all the brands of existing workflowconfiguration and another method that gets me all the brands of non existing workflow configuration.
I am lost cause I dont know where to start.
public class Brand
{
public int BrandId { get; set; }
public string Name { get; set; }
}
public class WorkflowType
{
public int WorkflowTypeId { get; set; }
public string Name { get; set; }
}
public class WorkflowConfiguration
{
public int WorkflowConfigurationId { get; set; }
public WorkflowType WorkflowType { get; set; }
public Brand Brand { get; set; }
public virtual ICollection<Approver> Approvers { get; set; }
}
Update1
Here are how my tables would look like and the expected result
Brand
Audi
Volkswagen
Mercedes
WorkflowTYpes
type 1
type 2
type 3
WorkflowConfiguration
brandid, workflowtype id
1 ------------ 1
1 -------------2
List<string> GetBrandsForExistingWorkflowType(string worfklowtype)
If I pass type1 to this method it should return:
Audi because for type1, audi exists on the table
List<string> GetBrandsForNonExistingWorkflowType(string workflowType)
If I pass type1 to this method it should return.
Volkswagen and Mercedes because for type1, those 2 brands are not in the relationship.
I think this is what you want:
List<string> GetBrandsForExistingWorkflowType(string worfklowtype)
{
var result = db.WorkflowConfigurations
.Where(x => x.WorkflowType.Name == worfklowtype)
.Select(x => x.Brand);
return result;
}
List<string> GetBrandsForNonExistingWorkflowType(string workflowType)
{
var excluded = GetBrandsForExistingWorkflowType(string worfklowtype);
var result = db.Brands.Except(excluded);
return result;
}

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