2012-02-01 4 views
34

У нас возникли проблемы с проектированием нашего многопоточного приложения с платформой Entity Framework и хотелось бы получить некоторые рекомендации. Мы создаем объекты в разных потоках, объекты добавляются в коллекции, которые затем привязываются к различным элементам управления wpf. Класс ObjectContext не является потокобезопасным, поэтому, управляя этим, у нас есть по существу два решения:Entity Framework и многопоточность

Решение 1 имеет единый контекст и тщательно использует блокировку, чтобы гарантировать, что 2 потока не обращаются к нему в одно и то же время. Это было бы относительно просто реализовать, но потребовало бы, чтобы контекст был живым на протяжении всего приложения. Неплохо ли идея открыть один экземпляр контекста?

Решение 2 - создавать объекты контекста по требованию, а затем немедленно отделять объекты, а затем удерживать их в наших собственных коллекциях, а затем повторно присоединять их для любого обновления. У этого есть некоторые серьезные проблемы для использования, хотя, как при отсоединении объектов они теряют ссылки на объекты свойств навигации. Также есть проблема, что 2 потока могут пытаться получить доступ к одному объекту, и оба пытаются связать() с контекстом. Кроме того, нам нужно будет предоставлять новый контекст каждый раз, когда мы хотим получить доступ к свойствам навигации сущностей.

В: Являются ли какие-либо из двух решений действительными, если не так, как вы порекомендуете нам заняться этим?

+2

@usr у вас есть идея лучше? – Cocowalla

+0

@Cocowalla, не зная, какой большой сценарий обращается к OP, я этого не делаю. Оба его решения приведут к мучительной реализации, поэтому я предупреждаю его. Может быть, он может пойти совсем по другому пути и использовать EF однопоточным способом (как это предполагается использовать). – usr

+0

Еще одно замечание: вы НЕ МОЖЕТЕ вносить какие-либо изменения в объект, если он отсоединен, поскольку ни один контекст в настоящее время не отслеживает это изменение. Это изменение не будет сохраняться при вызове SaveChanges(). – JoeCool

ответ

23

Прежде всего, я предполагаю, что вы прочитали this article on MSDN.

Решение №1 почти наверняка является самым безопасным с точки зрения пронизывания, поскольку вы гарантируете, что только один поток взаимодействует с контекстом при любом времени. Нет ничего неотъемлемо неправильного в том, чтобы поддерживать контекст вокруг - он не поддерживает открытия базы данных за кулисами, так что это просто издержки памяти. Разумеется, это может привести к проблемам с производительностью, если вы столкнулись с этим потоком, и все приложение написано с допущениями с одним db-потоком.

Решение №2 кажется мне неработоспособным - у вас появятся тонкие ошибки во всем приложении, где люди забывают повторно присоединить (или отсоединить) объекты.

Одним из решений является не использовать объекты объекта в слое пользовательского интерфейса приложения. Я бы порекомендовал это в любом случае - скорее всего, структура/макет объектов сущности не оптимальна для того, как вы хотите отображать вещи в своем пользовательском интерфейсе (это является причиной семейства шаблонов MVC). Ваш DAL должен иметь методы, специфичные для бизнес-логики (например, UpdateCustomer), и он должен сам решать, создавать ли новый Контекст или использовать сохраненный. Вы можете начать с единого хранимого контекста, а затем, если у вас возникнут проблемы с узким местом, у вас есть ограниченная площадь поверхности, где вам нужно внести изменения.

Недостатком является то, что вам нужно написать намного больше кода - у вас будут ваши объекты EF, но у вас также есть бизнес-объекты, которые имеют повторяющиеся свойства и потенциально отличающиеся мощности многих объектов EF. Чтобы смягчить это, вы можете использовать фреймворки, такие как AutoMapper, для упрощения копирования свойств от объектов EF к бизнес-объектам и обратно.

+0

Крис, спасибо за ответ, это было очень полезно для разъяснения наших мыслей. Мы собираемся пойти с вашей рекомендацией, я думаю, что мы слишком увлеклись попыткой использовать объекты сущности для того, чтобы делать все. – MartinR

+2

Еще одна проблема с решением 1 - кэширование: контекст будет кэшировать объекты, чтобы данные становились устаревшими, когда они были изменены за пределами исполняемого экземпляра приложения. – Cocowalla

+0

Ссылка, которую вы предоставили, пролила много света для меня в том, как работает EF. Большое спасибо. – Brandon

0

Вы не хотите долго жить. В идеале они должны быть в течение жизни операции запроса/данных.

При работе с подобными проблемами я завершил реализацию хранилища, в котором кешированные объекты PK для данного типа, и разрешил «LoadFromDetached», который бы нашел объект в базе данных и «скопировал» все скалярные свойства, кроме PK к вновь прикрепленному объекту.

Производительность будет немного хитом, но она обеспечивает пуленепробиваемый способ обеспечения того, чтобы свойства навигации не искажались, «забывая» о них.

+0

Я недавно столкнулся с подобной проблемой и оказался ниже, что значительно улучшило производительность. Набрал неверное место, см. Мой ответ – Otake

0

Это было когда-то вопрос, но я недавно столкнулся с аналогичной проблемой и в итоге сделал нижеследующее, что помогло нам выполнить критерии эффективности.

Вы в основном разбиваете свой список на куски и обрабатываете их в спящих потоках многопоточным способом. Каждая новая нить также инициирует свой собственный uow, который требует привязки ваших объектов.

Одна вещь, о которой нужно знать, заключается в том, что ваша база данных должна быть включена для изоляции моментальных снимков; иначе вы можете столкнуться с тупиками. Вам нужно будет решить, подходит ли это для операции, которую вы выполняете, и связанного с ней бизнес-процесса. В нашем случае это было простое обновление продукта продукта.

Вам, вероятно, понадобится выполнить некоторые тесты, чтобы решить вопрос о лучшем размере куска, а также ограничить параллелизм, чтобы всегда был ресурс для завершения операции.

private void PersistProductChangesInParallel(List<Product> products, 
     Action<Product, string> productOperationFunc, 
     string updatedBy) 
    { 
     var productsInChunks = products.ChunkBy(20); 

     Parallel.ForEach(
      productsInChunks, 
      new ParallelOptions { MaxDegreeOfParallelism = 20 }, 
      productsChunk => 
       { 
        try 
        { 
         using (var transactionScope = new TransactionScope(
           TransactionScopeOption.Required, 
           new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot })) 
         { 
          var dbContext = dbContextFactory.CreatedbContext(); 
          foreach (var Product in productsChunk) 
          { 
           dbContext.products.Attach(Product); 
           productOperationFunc(Product, updatedBy); 
          } 
          dbContext.SaveChanges(); 
          transactionScope.Complete(); 
         } 
        } 
        catch (Exception e) 
        { 
         Log.Error(e); 
         throw new ApplicationException("Some products might not be updated", e); 
        } 
       }); 
    } 
7

У меня есть дюжина потоков stackoverflow, касающихся EF и многопоточности. У всех из них есть ответы, которые подробно объясняют проблему, но на самом деле не показывают, как ее исправить.

EF не является потокобезопасным, мы все знаем это. Но, по моему опыту, единственный риск - это контекстные творения/манипуляции. На самом деле есть очень простое решение для этого, где вы можете хранить свою ленивую загрузку.

Допустим, у вас есть приложение WPF и веб-сайт MVC. где приложение WPF использует многопоточность. Вы просто располагаете контекст db в многопоточности и сохраняете его, когда нет. Пример веб-сайта MVC, контекст будет автоматически размещен после представления представления.

В слое приложения WPF используется следующим образом:

ProductBLL productBLL = new ProductBLL(true); 

В слое приложения MVC используется следующим образом:

ProductBLL productBLL = new ProductBLL(); 

Как ваш продукт бизнес-логика слой должен выглядеть :

public class ProductBLL : IProductBLL 
{ 
    private ProductDAO productDAO; //Your DB layer 

    public ProductBLL(): this(false) 
    { 

    } 
    public ProductBLL(bool multiThreaded) 
    { 
     productDAO = new ProductDAO(multiThreaded); 
    } 
    public IEnumerable<Product> GetAll() 
    { 
     return productDAO.GetAll(); 
    } 
    public Product GetById(int id) 
    { 
     return productDAO.GetById(id); 
    } 
    public Product Create(Product entity) 
    { 
     return productDAO.Create(entity); 
    } 
    //etc... 
} 

Как ваша логика слой базы данных должен выглядеть следующим образом:

public class ProductDAO : IProductDAO 
{ 
    private YOURDBCONTEXT db = new YOURDBCONTEXT(); 
    private bool _MultiThreaded = false; 

    public ProductDAO(bool multiThreaded) 
    { 
     _MultiThreaded = multiThreaded; 
    } 
    public IEnumerable<Product> GetAll() 
    { 
     if (_MultiThreaded) 
     { 
      using (YOURDBCONTEXT db = new YOURDBCONTEXT()) 
      { 
       return db.Product.ToList(); //USE .Include() For extra stuff 
      } 
     } 
     else 
     { 
      return db.Product.ToList(); 
     }     
    } 

    public Product GetById(int id) 
    { 
     if (_MultiThreaded) 
     { 
      using (YOURDBCONTEXT db = new YOURDBCONTEXT()) 
      { 
       return db.Product.SingleOrDefault(x => x.ID == id); //USE .Include() For extra stuff 
      } 
     } 
     else 
     { 
      return db.Product.SingleOrDefault(x => x.ID == id); 
     }   
    } 

    public Product Create(Product entity) 
    { 
     if (_MultiThreaded) 
     { 
      using (YOURDBCONTEXT db = new YOURDBCONTEXT()) 
      { 
       db.Product.Add(entity); 
       db.SaveChanges(); 
       return entity; 
      } 
     } 
     else 
     { 
      db.Product.Add(entity); 
      db.SaveChanges(); 
      return entity; 
     } 
    } 

    //etc... 
} 
0

Я только что проект, в котором пытаются использовать EF с многопоточность причиной ошибок.

Я попытался

using (var context = new entFLP(entity_connection))    
{ 
    context.Product.Add(entity); 
    context.SaveChanges(); 
    return entity; 
} 

, но он просто изменил тип ошибки от ошибок DataReader к многократному ошибки потока.

Простое решение заключается в использовании хранимых процедур с импортом функции EF

using (var context = new entFLP(entity_connection)) 
{ 
    context.fi_ProductAdd(params etc); 
} 

Ключ должен идти к источнику данных и избежать модели данных.