tags:

views:

59

answers:

3

I wrote the following method that receives a list and updates the database based on certain criteria:

 public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
        {
            int index = 0;

            var query = from inventoryGoods in context.InventoryGoods
                        where inventoryGoods.ParentId == id
                        select inventoryGoods;

            List<InventoryGoods> goodsList = query.ToList();

            using (var scope = new TransactionScope())
            {
                foreach (InventoryGoods i in list)
                {
                    foreach (InventoryGoods e in goodsList)
                    {
                        if (index == 30)
                        {
                            index = 0;
                            context.SubmitChanges();
                        }

                        if (e.Gid == i.Gid && !getEventId(e.Id).HasValue && !e.ActionOn.HasValue)
                        {
                            e.Action = i.Action;

                        }
                        else if ((e.Gid == i.Gid && getEventId(e.Id).HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
                        {
                            e.Action = i.Action;
                            e.ActionOn = null;

                            var allEvents = from invent in context.InventoryGoodsEvents
                                            where invent.InventoryGood == e.Id
                                            select invent;

                            List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();

                            var events = from g in context.GoodsEvent                                         
                                         select g;

                            List<GoodsEvent> goodsEventList = events.ToList();

                            foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
                            {
                                context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);

                                foreach (GoodsEvent ge in goodsEventList)
                                {
                                    if (ge.Id == goodsEvent.EventId)
                                    {
                                        ge.IsDeleted = true;
                                        ge.DeletedOn = DateTime.Now;
                                        ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
                                    }
                                }                                
                            }




                        }
                        ++index;
                    }
                }
                context.SubmitChanges();
                scope.Complete();
            }

        }

        public int? getEventId(int InventoryGood)
        {


            var InventoryGoodsEvents = from i in context.InventoryGoodsEvents
                                       where i.InventoryGood == InventoryGood
                                       select i;

            List<InventoryGoodsEvents> lst = InventoryGoodsEvents.ToList();

            if (lst.Count() > 0)
            {
                return lst[0].EventId;
            }
            else
            {
                return null;
            }
        }

Though this method works well for about 500 or 1000 objects, it gets too slow or eventually times out when I feed it over 8000 objects or more. So, where could I improve its performance a little?

A: 

I'm no Linq expert, but I think you can probably improve getEventId (should be capital first letter btw) with something like

public int? GetEventId(int inventoryGood)
{
    var firstInventoryGoodsEvent = context.InventoryGoodsEvents
                  .Where(i => i.InventoryGood == inventoryGood)
                  .FirstOrDefault();

    // ...etc
}

The use of FirstOrDefault() means you don't process the whole list if you find a matching element.

There are probably other optimisations but it's quite difficult to follow what you're doing. As an example:

 foreach (InventoryGoods i in list)
 {
     foreach (InventoryGoods e in goodsList)
     {
     }
 }

i and e don't confer much meaning here. It might be obvious to you what they mean but they aren't very descriptive to someone who has never seen your code before. Similarly, list is not the best name for a List. List of what? Your variable name should describe it's purpose.

Edit:

I'm not sure about anything else. You seem to be using ToList() in a few places where as far as I can see it's not necessary. I don't know what effect that would have on performance, but someone cleverer than me could probably tell you.

You could also try hoisting a few of your values outside of loops, eg:

foreach (foo)
{
    foreach (bar)
    {
        DeletedOn = DateTime.Now;
        DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
    }
}

can be re-written as

var deletedOn = DateTime.Now;
var deletedBy = System.Web.HttpContext.Current.User.Identity.Name;

foreach (foo)
{
    foreach (bar)
    {
        DeletedOn = deletedOn;
        DeletedBy = deletedBy;
    }
}

Again, I'm not sure how much difference if any that would make, you'll need to test it and see.

fearofawhackplanet
I like your suggestion and if you have more, I would really appreciate it. Right now I need to improve its performance as much as possible because it cannot give timeouts at the end user...
Hallaghan
i is what I got from the View (this is MVC) and e is what is stored in the database. I compare them against each other to see what's been modified in order to update.
Hallaghan
Well thanks, I'll take all your tips into consideration and try to improve it further.
Hallaghan
Ok, good luck and let us know how it goes!
fearofawhackplanet
A: 

It's not going in batches of 30, it's going in batches of 1.

There's a query with no criteria, so it loads the whole table. Is that your intention?

getEventId(e.Id) returns a consistent value. Don't call it twice (per loop).

David B
Yes, it's intentional. But how is it not going in batches of 30?
Hallaghan
Each iteration of the loop, index is incremented. An update is not made in each iteration of the loop. In fact, the vast majority of loop iterations produce no update (i.Gid == e.Gid)
David B
A: 

Don't call the database in a loop.

Try moving the queries outside the loops like this:

 public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
 {
     int index = 0;

     var query = from inventoryGoods in context.InventoryGoods
                 where inventoryGoods.ParentId == id
                 select inventoryGoods;

     List<InventoryGoods> goodsList = query.ToList();

     using (var scope = new TransactionScope())
     {
         var allEvents = from invent in context.InventoryGoodsEvents
                         where goodsList.Contains(invent.InventoryGood)
                         select invent;

         List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();

         var events = from g in context.GoodsEvent
                      select g;

         List<GoodsEvent> goodsEventList = events.ToList();

         foreach (InventoryGoods i in list)
         {
             foreach (InventoryGoods e in goodsList)
             {
                 if (index == 30)
                 {
                     index = 0;
                     context.SubmitChanges();
                 }

                 var eventId = getEventId(e.Id);
                 if (e.Gid == i.Gid && !eventId.HasValue && !e.ActionOn.HasValue)
                 {
                     e.Action = i.Action;

                 }
                 else if ((e.Gid == i.Gid && eventId.HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
                 {
                     e.Action = i.Action;
                     e.ActionOn = null;

                     foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
                     {
                         context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);

                         foreach (GoodsEvent ge in goodsEventList)
                         {
                             if (ge.Id == goodsEvent.EventId)
                             {
                                 ge.IsDeleted = true;
                                 ge.DeletedOn = DateTime.Now;
                                 ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
                             }
                         }
                     }
                 }
                 ++index;
             }
         }
         context.SubmitChanges();
         scope.Complete();
     }
 }
asgerhallas
Even after trying your changes, I can't seem to get the method any faster... it's a really high load of data. Can you give me any further help please?
Hallaghan
I can try :) What's the reason you submit in batches?
asgerhallas
Because it updates generally 8000 or more rows at once and it proves to be extremly slow in a browser application.
Hallaghan