2

I'm retrieving two lists of datasets using Linq to entities. They're both in the same Database, but I need to get one table converted into my Tasks Table because it's integrated into my Calendar. Not worth going into extreme detail here I'm sure but I would love to speed up the process of matching id's and Creating new Task objects. This is a Once and done snippet so even at a slow speed I can simply leave the program running overnight. However, for future reference I'd like some suggestions on increasing the efficiency.

var accounts = data.Accounts.ToList().OrderBy(a => a.ID);
Incidents[] Incidents = data.Incidents.ToArray();

        for (int i=0;i<Incidents.Length;i++)
        {
            foreach (var a in accounts)
            {
                if (a.Acct_CID == Incidents[i].CustomerID)
                {
                    Tasks t = new Tasks();
                    t.creator_id = a.ID;
                    t.start_date = Incidents[i].DateOpened;
                    t.end_date = Incidents[i].DateCLosed;
                    t.product_code = Incidents[i].ProductCode;
                    t.install_type = Incidents[i].InstallType;
                    t.os = Incidents[i].OSType;
                    t.details = Incidents[i].Description;
                    t.solution = Incidents[i].Solution;
                    t.creator_name = Incidents[i].TechID;
                    t.category = Incidents[i].Title;
                    t.text = "Ticket for" + " " + Incidents[i].Name;
                    if (t.end_date == DateTime.MinValue || t.end_date == null)
                        t.status_id = 6;
                    else t.status_id = 7;
                    data.Tasks.Add(t);
                    break;
                }
            }
        }
        data.SaveChanges();
Sergey Berezovskiy
  • 215,927
  • 33
  • 392
  • 421
Chazt3n
  • 1,571
  • 3
  • 14
  • 40

5 Answers5

3

Why not to join tables and create tasks on the fly?

var tasks = from i in data.Incidents
            join a in data.Accounts on i.CustomerID equals a.Acct_CID
            select new Tasks()
            {
                creator_id = a.ID,
                start_date = i.DateOpened,
                end_date = i.DateCLosed
                // ...
            };

BTW I don't think ordering makes sense here thus it should not matter in which order you are adding created tasks to database.

// Query will not be executed until here
foreach(var task in tasks)
   data.Tasks.Add(task);
data.SaveChanges();
Sergey Berezovskiy
  • 215,927
  • 33
  • 392
  • 421
  • Sorry, the ordering is for a separate Method not shown in the Code, it's an enormous project. I'm dumbfounded I didn't think of that. Running coffeeless today, thank you for the help sir – Chazt3n Oct 15 '12 at 21:16
  • I'm surpised I didn't think of it either. – Bobson Oct 15 '12 at 21:18
  • 1
    @Chazt3n welcome :) BTW call ToList(), ToArray() and other To* operations as late, as possible. All of them force query execution and later processing will happen only in-memory. – Sergey Berezovskiy Oct 15 '12 at 21:20
  • The entity or complex type 'TRX.CRM.Dashboard.Web.UI.Models.Tasks' cannot be constructed in a LINQ to Entities query. – Chazt3n Oct 16 '12 at 13:22
  • 1
    @Chazt3n you can return anonymous object and map in to your tasks entity – Sergey Berezovskiy Oct 16 '12 at 14:26
  • @lazyberezovsky would you be willing to show how in the code? – Chazt3n Oct 16 '12 at 14:53
  • @Chazt3n please, see this question for problem of returning mapped entities http://stackoverflow.com/questions/5325797/the-entity-cannot-be-constructed-in-a-linq-to-entities-query – Sergey Berezovskiy Oct 16 '12 at 15:01
  • I'm not sure I understand what I'm doing differently in my new edit, aside from not having a seperate method – Chazt3n Oct 16 '12 at 16:57
3

I would Join the result on DB

var joinedResult = data.Accounts.Join(data.Incidents, 
                                      a => a.Acct_CID, 
                                      i => i.CustomerID, 
                                      (a, i) => new { Account = a, Incident = i });

foreach (var item in joinedResult)
{
    Tasks t = new Tasks();
    t.creator_id = item.Account.ID;
    t.start_date = item.Incident.DateOpened;
    ........

}
L.B
  • 106,644
  • 18
  • 163
  • 208
  • If I could've I would mark both your answers are correct It was literally a coin toss, I did give you a +1 and thank you for the help you guys are great. – Chazt3n Oct 15 '12 at 21:18
  • It doesn't seem to iterate through anything in this instance? am I just unable to see this iteration or does it actually not think there is a list? – Chazt3n Oct 16 '12 at 21:20
  • Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. I'm getting this error – Chazt3n Oct 16 '12 at 21:44
  • I can fix it by calling tolist() on data.Accounts and data.Incidents but then it's even slower than the nested loops. – Chazt3n Oct 16 '12 at 21:45
1

Replace this line

var accounts = data.Accounts.ToList().OrderBy(a => a.ID);

with this

var accounts = data.Accounts.OrderBy(a => a.ID).ToList();

That will let the database do the sort, then cache the sorted results. What you have now pulls in everything, then sorts them each time you reach the foreach loop (accounts gets enumerated all over again).

I can't say it'll make a huge improvement, but if your data set is large enough, re-sorting a large list many times will certainly slow you down.


On second glance, you're not only sorting accounts every time, but you seem to be looking for only a small subset of the records, yet you're iterating over the whole array. Consider replacing

    foreach (var a in accounts)
        {
            if (a.Acct_CID == Incidents[i].CustomerID)
            {

with

      foreach (var a in accounts.Where(acct => acct.Acct_CID == Incidents[i].CustomerID))
      {
Bobson
  • 12,858
  • 4
  • 49
  • 78
  • 1
    @Chazt3n - It's not just OrderBy. Where, Select, Group, etc... Everything that returns an IEnumerable will re-evaluate every time you enumerate it (such as each time you start a new `foreach`). It's good practice to always make `ToList()`, `ToDictionary()`, and so on the last thing you do before you use the data. – Bobson Oct 15 '12 at 21:20
1

Create a lookup of the accounts

var accountsLookup = data.Accounts.ToLookup(a => a.Acct_CID);
foreach (var incident in data.Incidents)
{
    foreach (var a in accountsLookup[incident.CustomerID])
    {
        Tasks t = new Tasks();
        t.creator_id = a.ID;
        ...
    }
}
data.SaveChanges();

If the accounts are unique you could also create a dictionary

var accountsDict = data.Accounts.ToDictionary(a => a.Acct_CID);
foreach (var incident in data.Incidents)
{
    Account a;
    if (accountsDict.TryGetValue(incident.CustomerID, out a)
    {
        Tasks t = new Tasks();
        t.creator_id = a.ID;
        ...
    }
}
data.SaveChanges();

This would be faster than the first variant. Note that dictionaries have a constant lookup time that does not depend on its size. Therefore you basically get an O(n) execution time for the loop. Your original implementation has an O(n^2) execution time.

Olivier Jacot-Descombes
  • 86,431
  • 10
  • 121
  • 160
0
    var tasks = (from i in data.Incidents
                     join a in data.Accounts on i.CustomerID equals a.Acct_CID
                     select new
                     {
                         creator_id = a.ID,
                         start_date = i.DateOpened,
                         end_date = i.DateCLosed,
                         product_code = i.ProductCode,
                         install_type = i.InstallType,
                         os = i.OSType,
                         details = i.Description,
                         solution = i.Solution,
                         creator_name = i.TechID,
                         category = i.Title,
                         text = "Ticket for" + " " + i.Name,
                         status_id = 7
                     }).AsEnumerable().Select(x => new
                         {
                             x.creator_id,
                             x.start_date,
                             x.end_date,
                             x.product_code,
                             x.os,
                             x.details,
                             x.solution,
                             x.creator_name,
                             x.category,
                             x.text,
                             x.install_type,
                             x.status_id
                         });


        foreach (var item in tasks)
        {
            Tasks t = new Tasks();
            t.os = item.os;
            t.id = item.creator_id;
            t.install_type = item.install_type;
            t.start_date = item.start_date;
            t.end_date = item.end_date;
            t.solution = item.solution;
            t.details = item.details;
            t.creator_name = item.creator_name;
            t.category = item.category;
            t.text = item.text;
            t.product_code = item.product_code;
             if (t.end_date == DateTime.MinValue || t.end_date == null)
                 t.status_id = 6;
             else t.status_id = 7;
             data.Tasks.Add(t);
        }
        data.SaveChanges();
Chazt3n
  • 1,571
  • 3
  • 14
  • 40
  • This is what ultimately Fixed the problem, thank you for your help and time, however I did have to start a new question to have the issue resolved. – Chazt3n Oct 17 '12 at 16:06
  • Why have you unaccepted my answer, if you used my query and did as I said `you can return anonymous object and map it to your tasks entity`? – Sergey Berezovskiy Oct 17 '12 at 16:12
  • Because I made it clear I didn't understand how to achieve this with Linq to entities and I wanted you to feel bad for a split second. – Chazt3n Oct 17 '12 at 16:19
  • You did it well :) Btw it's not good to completely change your question, because others will read answers and they will not understand what's going on. It's better to start new question, or write updates under original question. Thus I rolled back editions to yesterday's version – Sergey Berezovskiy Oct 17 '12 at 16:22
  • ok awesome thanks for the help, I'm still new to SO. It's a great place, so many nuances. I'll get the hang of it, and thank you for real for helping me figure that out. – Chazt3n Oct 17 '12 at 16:26
  • No problem, sorry about not responding to your comments - didn't see that notification today – Sergey Berezovskiy Oct 17 '12 at 16:27
  • 1
    And one more trick for your program - take a look at http://automapper.org It will do mapping from TasksDTO to Tasks behind the scene. – Sergey Berezovskiy Oct 17 '12 at 16:29
  • No worries, we all have lives afterall my bad for being a pain – Chazt3n Oct 17 '12 at 16:29