Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good advices to use EF in a multithread program?

Have you got some good advices to use EF in a multithread program ?

I have 2 layers :

  • a EF layer to read/write into my database
  • a multithread service which uses my entities (read/write) and makes some computations (I use Task Parallel Library in the framework)

How can I synchronize my object contexts in each thread ? Do you know a good pattern to make it work ?

like image 217
Patrice Pezillier Avatar asked Oct 15 '22 03:10

Patrice Pezillier


2 Answers

Good advice is - just don't :-) EF barely manages to survive one thread - the nature of the beast.

If you absolutely have to use it, make the lightest DTO-s, close OC as soon as you have the data, repack data, spawn your threads just to do calculations and nothing else, wait till they are done, then create another OC and dump data back into DB, reconcile it etc.

If another "main" thread (the one that spawns N calculation threads via TPL) needs to know when some ther thread is done fire event, just set a flag in the other thread and then let it's code check the flag in it's loop and react by creating new OC and then reconciling data if it has to.

If your situation is more simple you can adapt this - the key is that you can only set a flag and let another thread react when it's ready. That means that it's in a stable state, has finished a round of whatever it was doing and can do things without risking race conditions. Reset the flag (an int) with interchaged operations and keep some timing data to make sure that your threads don't react again within some time T - otherwire they can spend their lifetime just querying DB.

like image 130
ZXX Avatar answered Oct 19 '22 01:10

ZXX


This is how I implemented it my scenario.

var processing= new ConcurrentQueue<int>();


//possible multi threaded enumeration only processed non-queued records
Parallel.ForEach(dataEnumeration, dataItem=>
{
     if(!processing.Contains(dataItem.Id))
     {
         processing.Enqueue(dataItem.Id);

          var myEntityResource = new EntityResource();

          myEntityResource.EntityRecords.Add(new EntityRecord
                                      {
                                        Field1="Value1", 
                                        Field2="Value2"
                                      }
                               );

           SaveContext(myEntityResource);

       var itemIdProcessed = 0;
       processing.TryDequeue(out itemIdProcessed );

     }

}

public void RefreshContext(DbContext context)
    {
        var modifiedEntries = context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Modified || e.State == EntityState.Deleted);
        foreach (var modifiedEntry in modifiedEntries)
        {
            modifiedEntry.Reload();
        }
    }

public bool SaveContext(DbContext context,out Exception error, bool reloadContextFirst = true)
    {
        error = null;
        var saved = false;
        try
        {
            if (reloadContextFirst)
                this.RefreshContext(context);
            context.SaveChanges();
            saved = true;
        }
        catch (OptimisticConcurrencyException)
        {
            //retry saving on concurrency error
            if (reloadContextFirst)
                this.RefreshContext(context);
            context.SaveChanges();
            saved = true;
        }
        catch (DbEntityValidationException dbValEx)
        {
            var outputLines = new StringBuilder();
            foreach (var eve in dbValEx.EntityValidationErrors)
            {
                outputLines.AppendFormat("{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
                    DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State);
                foreach (var ve in eve.ValidationErrors)
                {
                    outputLines.AppendFormat("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
                }
            }

            throw new DbEntityValidationException(string.Format("Validation errors\r\n{0}", outputLines.ToString()), dbValEx);
        }
        catch (Exception ex)
        {
            error = new Exception("Error saving changes to the database.", ex);
        }
        return saved;
    }
like image 40
dynamiclynk Avatar answered Oct 19 '22 03:10

dynamiclynk