views:

55

answers:

4

On my ASP.NET MVC application I'm running a couple of inserts at once that can insert 10000 or more lines and update a few others. This process is taking a long time but I can't escape the insert because that's exactly what I was asked to do. Right now I'm running Sql Server Profiler and it takes almost 20 minutes to insert this bunch of rows. How could I possibly improve the performance of this action?

(I'm using Linq-to-Sql to insert the data into the database.)

This is the code of the method doing the inserts:

   [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult SaveEvent(int id)
        {
            int eventID= 0;
            var query = from q in context.InventoryGoods
                        where q.ParentId == id && q.Action.HasValue && q.ActionOn.HasValue == false
                        select q;

            var stockType = from q in context.Inventory
                            where q.Id == id
                            select q.StockType;

            if (query.Count() > 0)
            {
                foreach (var i in query)
                {
                        switch (i.Action.Value)
                        {
                            case (int)InventoryGoodsActionEnum.AdjustLocation:

                                Guid guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "LO",
                                        Lid = i.LidObtained,
                                        Comments = "Inventário "+i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }



                                break;

                            case (int)InventoryGoodsActionEnum.AdjustQuantity:

                                if (!i.QuantityObtained.HasValue)
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now,
                                            EventOn = DateTime.Now,
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityExpected * -1),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }

                                }
                                else if ((i.QuantityObtained - (i.QuantityExpected.HasValue ? i.QuantityExpected : 0) != 0))
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now,
                                            EventOn = DateTime.Now,
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityObtained.Value - (i.QuantityExpected.HasValue ? i.QuantityExpected : 0)),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }
                                }


                                break;

                            case (int)InventoryGoodsActionEnum.AdjustQuantityLocation:

                                guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "LO",
                                        Lid = i.LidExpected,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }
                                if (!i.QuantityObtained.HasValue)
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityExpected * -1),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }

                                }
                                else if ((i.QuantityObtained - (i.QuantityExpected.HasValue ? i.QuantityExpected : 0) != 0))
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityObtained.Value - (i.QuantityExpected.HasValue ? i.QuantityExpected : 0)),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }
                                }



                                break;

                            case (int)InventoryGoodsActionEnum.AdjustStockType:

                                guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "ST",
                                        StockType = stockType.First().Value,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }


                                break;
                            case (int)InventoryGoodsActionEnum.AdjustLocationStockType:

                                guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "ST",
                                        StockType = stockType.First().Value,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }

                                 guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now.AddSeconds(1),
                                        EventOn = DateTime.Now.AddSeconds(1),
                                        Type = "LO",
                                        Lid = i.LidExpected,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };



                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }



                                break;
                            case (int)InventoryGoodsActionEnum.AdjustQuantityStockType:

                                guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "ST",
                                        StockType = stockType.First().Value,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }

                                if (!i.QuantityObtained.HasValue)
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityExpected * -1),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }

                                }
                                else if ((i.QuantityObtained - i.QuantityExpected != 0))
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityObtained.Value - i.QuantityExpected),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }
                                }


                                break;
                            case (int)InventoryGoodsActionEnum.AdjustQuantityLocationStockType:

                                guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now,
                                        EventOn = DateTime.Now,
                                        Type = "ST",
                                        StockType = stockType.First().Value,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };

                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }

                                if (!i.QuantityObtained.HasValue)
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityExpected * -1),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }

                                }
                                else if ((i.QuantityObtained - i.QuantityExpected != 0))
                                {
                                    guid = Guid.NewGuid();

                                    using (var scope = new TransactionScope())
                                    {
                                        GoodsEvent ge = new GoodsEvent()
                                        {
                                            Gid = i.Gid,
                                            Guid = guid,
                                            CreatedOn = DateTime.Now.AddSeconds(1),
                                            EventOn = DateTime.Now.AddSeconds(1),
                                            Type = "AQ",
                                            Quantity = (short)(i.QuantityObtained.Value - i.QuantityExpected),
                                            Comments = "Inventário " + i.ParentId,
                                            UserId = GetUserId(),
                                        };

                                        context.GoodsEvent.InsertOnSubmit(ge);

                                        context.SubmitChanges();
                                        eventID = ge.Id;
                                        Repository.SetActionOn(i.Id, eventID);

                                        scope.Complete();
                                    }
                                }

                                 guid = Guid.NewGuid();

                                using (var scope = new TransactionScope())
                                {
                                    GoodsEvent ge = new GoodsEvent()
                                    {
                                        Gid = i.Gid,
                                        Guid = guid,
                                        CreatedOn = DateTime.Now.AddSeconds(2),
                                        EventOn = DateTime.Now.AddSeconds(2),
                                        Type = "LO",
                                        Lid = i.LidExpected,
                                        Comments = "Inventário " + i.ParentId,
                                        UserId = GetUserId(),
                                    };



                                    context.GoodsEvent.InsertOnSubmit(ge);

                                    context.SubmitChanges();
                                    eventID = ge.Id;
                                    Repository.SetActionOn(i.Id, eventID);

                                    scope.Complete();
                                }




                                break;
                    }
                }
            }
            else
            {
                var lista = from q in context.InventoryGoods
                            where q.ParentId == id
                            select q;

                Repository.EvaluateActions(lista.ToList());

                SaveEvent(id);
            }


            using (var scope = new TransactionScope())
            {
                var thisInventory = from i in context.Inventory
                                    where i.Id == id
                                    select i;

                thisInventory.First().State = (int)InventoryStateEnum.Verified;

                context.SubmitChanges();

                scope.Complete();
            }

            Status.Info(string.Format("Acções aplicadas com sucesso."));
            return RedirectToAction("Details", new { id });
        }



 public void SetActionOn(int id, int eventID)
        {
            var InventoryGoods = from i in context.InventoryGoods
                                 where i.Id == id
                                 select i;



            using (var scope = new TransactionScope())
            {
                InventoryGoods.First().ActionOn = DateTime.Now;

                InventoryGoodsEvents ige = new InventoryGoodsEvents
                {
                    EventId = eventID,
                    InventoryGood = InventoryGoods.First().Id,
                };

                context.InventoryGoodsEvents.InsertOnSubmit(ige);

                scope.Complete();
            }
        }
A: 

What DAL do you use EF, L2S, ADO.net or something else? Insert shouldn't take so much time to complete. You can insert them to local cache, and submit changes later.

Wyvern
We're using ADO.NET.
Hallaghan
After trying to insert 8500 lines, it took the system 30 minutes to complete the operation.
Hallaghan
OK, got it. Look at this pattern below:context.GoodsEvent.InsertOnSubmit(ge);context.SubmitChanges();Just move "context.SubmitChanges();" statement out of scope even out of foreach loop.SubmitChanges is a time consuming operation, it sync local cache and remote Sql DB data status. If you insert 8500 lines, it will sync 8500 times right? But after move SubmitChanges() out of loop, it only sync one time.
Wyvern
@Wyvern- I'm testing with Context.SubmitChanges outside the loop right now.
Hallaghan
And it completed with the following error:Exception Details: System.Data.SqlClient.SqlException: Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.Source Error: Line 729: }Line 730: }Line 731: context.SubmitChanges();Line 732: }Line 733: else
Hallaghan
The optimized pattern should be:using (var scope = new TransactionScope()){foreach (var i in query){...context.GoodsEvent.InsertOnSubmit(ge);...}context.SubmitChanges();//Add exception handling here, you can rollback operation.scope.Complete();}
Wyvern
I will try it that way, should have known better that there was something wrong with my code. Thanks for your support!
Hallaghan
A: 

Not C# programmer, but i suggest "batch update".

foret
+1  A: 

Linq-to-sql really wasn't designed for inserting that many records to the database in one batch. It will do it insert statement by insert statement which is really slow. I'd recommend that anywhere where you know you'll need to support this many inserts that you use the SqlBulkCopy object instead of your Linq-to-sql classes. You could even still use your same L2S classes if you need them for object validation, but then just dump them into a DataTable in 1000 row chunks and let SqlBulkCopy do your actual inserts. You could even google L2S and SqlBulkCopy and see what's out there as far as extension methods or other integration. You aren't the first one to run into this problem.

mattmc3
+1 for `SqlBulkCopy`
abatishchev
Have you looked at the code of my method above? Do you think I could improve it to make it any faster? I'm wondering whether I could run those "context.SubmitChanges();" just in the end of my switch or if I need to run them as they're ran now. What do you think?
Hallaghan
It wasn't there when I posted my answer, but at first glance there's a lot of duplication of code. This might be ripe for a refactor **before** optimizing the insert performance.
mattmc3
+1  A: 

Stop using var so much.


This runs the query twice (observe in the sqlprofiler).

if (query.Count() > 0) 
{ 
  foreach (var i in query) 

Use this to avoid running the query multiple times.

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

Your code has much repetition. Also, you're trying to do too little in the context in each Submit. You're controlling transaction scope for no reason.

foreach(InventoryGood i in rows)
{
  InventoryGoodsEvent ige = new InventoryGoodsEvent()
    //this will attach ige to the object graph tracked by context
    // which is sufficient to insert ige when submitchanges is called.
  ige.InventoryGood = i;

  GoodsEvent ge = GetGoodsEvent(i); //all that conditional logic in there.
    //this will attach ge to the object graph tracked by context
    // which will both insert ge and update ige with ge's id when submitchanges is called.
  ige.GoodsEvent = ge;

  i.ActionOn = DateTime.Now;
    //to submit each row, uncomment this.  
  //context.SubmitChanges();
}
 //to submit all rows at once, use this.
context.SubmitChanges();

If InventoryGoodEgvents doesn't have those relational properties, go into the designer and add associations to create them.

Once you have such code, then you get to decide what's a good amount of change to do in one transaction. I like to insert ~100 records per transaction. If you use 1 record per transaction, there's a high overhead of creating each transaction. If you use 1,000,000 rows per transaction, there's the high overhead of a long running transaction.

This stuff is hard to learn, but keep at it.

One more thing: Bulk insert isn't going to work with that many-to-many table.

David B
<subjective-comment-alert>There's nothing wrong with how var was used here. It's in conjunction with Linq queries, which everyone knows return IEnumerable or whatever you selected, and he's also used in in conjunction with the new keyword - which is much less redundant than the rediculous `TransactionScope scope = new TransactionScope();`. Var can be overused, but here it was certainly appropriate.</subjective-comment-alert>
mattmc3
There are certain declarations where var is required. Using var beyond that is a matter of style (as you say, subjective). Your chosen case saves a mere 13 characters. Perhaps if the line wasn't optimized for length, it wouldn't have been used so many times, in the end saving many many more characters. In the case of the comprehension query syntax result assignment, as you say "or whatever". "Or whatever" is the problem.
David B