2012-02-01 20 views
34

Mamy problemy z zaprojektowaniem naszej wielowątkowej aplikacji opartej na Entity Framework i chcielibyśmy uzyskać pewne wskazówki. Tworzymy jednostki na różnych wątkach, jednostki są dodawane do kolekcji, które są następnie danymi powiązanymi z różnymi kontrolkami wpf. Klasa ObjectContext nie jest bezpieczna dla wątków, więc zarządzając tym, mamy zasadniczo 2 rozwiązania:Entity Framework i Multi Threading

Rozwiązanie 1 ma jeden kontekst i ostrożnie korzysta z blokowania, aby zapewnić, że żaden wątek nie uzyska dostępu do niego w tym samym czasie. Byłoby to stosunkowo łatwe do wdrożenia, ale wymagałoby kontekstu, aby był aktywny przez cały czas trwania aplikacji. Czy jest to zły pomysł, aby otworzyć pojedynczą instancję kontekstu?

Rozwiązanie 2 polega na tworzeniu obiektów kontekstowych na żądanie, a następnie natychmiastowym odłączaniu obiektów, a następnie trzymaniu ich w naszych własnych kolekcjach, a następnie ponownym dołączaniu do nich w celu aktualizacji. Ma to jednak poważne problemy z używaniem, ponieważ gdy obiekty są odłączane, tracą odniesienia do obiektów właściwości nawigacji. Istnieje również problem, że 2 wątki nadal mogą próbować uzyskać dostęp do pojedynczego obiektu, a oba starają się dołączyć() do kontekstu. Musielibyśmy również podać nowy kontekst za każdym razem, gdy chcemy uzyskać dostęp do właściwości nawigacji jednostek.

P: Czy jedno z tych dwóch rozwiązań jest ważne, jeśli nie, w jaki sposób radzimy sobie z tym rozwiązaniem?

+2

@us masz lepszy pomysł? – Cocowalla

+0

@Cocowalla nie znając większego scenariusza, do którego adresuje OP, ja nie. Oba rozwiązania doprowadzą do bolesnej implementacji, dlatego ostrzegam go. Może on może wziąć zupełnie inną ścieżkę i użyć EF w sposób jednowątkowy (sposób, w jaki ma być używany). – usr

+0

Inną rzeczą, na którą należy zwrócić uwagę: NIE MOŻNA wprowadzać żadnych zmian w obiekcie, gdy jest on odłączony, ponieważ obecnie żaden kontekst nie śledzi tej zmiany. Zmiana nie będzie trwała, gdy później zostanie wywołana funkcja SaveChanges(). – JoeCool

Odpowiedz

23

Po pierwsze, zakładam, że przeczytałeś this article on MSDN.

Rozwiązanie # 1 jest prawie na pewno najbezpieczniejszym z punktu widzenia gwintowania, ponieważ gwarantujesz, że tylko jeden wątek wchodzi w interakcję z kontekstem w dowolnym momencie. Nie ma nic niewłaściwego w utrzymywaniu kontekstu - nie utrzymywanie połączeń z bazami danych za kulisami, więc jest to po prostu obciążenie pamięci. Może to oczywiście powodować problem z wydajnością, jeśli skończy się to wąskim gardłem w tym wątku, a cała aplikacja jest napisana z założeniami pojedynczej db.

Rozwiązanie nr 2 wydaje mi się niewykonalne - skończyłoby się subtelnym błędem w całej aplikacji, w którym ludzie zapomnieliby ponownie dołączyć (lub odłączyć) byty.

Jednym z rozwiązań jest nie używanie obiektów obiektu w warstwie interfejsu aplikacji. Mimo to i tak polecam - jest szansa, że ​​struktura/układ obiektów encji nie jest optymalny, jeśli chodzi o sposób wyświetlania elementów w interfejsie użytkownika (jest to powód, dla którego rodzina modeli MVC). Twój DAL powinien mieć metody, które są specyficzne dla logiki biznesowej (na przykład) i powinien zdecydować wewnętrznie, czy utworzyć nowy Kontekst czy użyć zapisanego. Możesz zacząć od pojedynczego zapisanego podejścia kontekstowego, a jeśli napotkasz problemy z wąskim gardłem, masz ograniczoną powierzchnię, na której musisz dokonać zmian.

Wadą jest to, że musisz napisać o wiele więcej kodu - masz swoje jednostki EF, ale masz również podmioty gospodarcze, które mają zduplikowane właściwości i potencjalnie różniące się liczności wielu jednostek EF. Aby to złagodzić, można użyć struktur, takich jak AutoMapper, aby uprościć kopiowanie właściwości z jednostek EF do jednostek biznesowych iz powrotem.

+0

Chris, dzięki za odpowiedź, było to bardzo pomocne w wyjaśnianiu naszych myśli. Chcemy pójść z twoją rekomendacją, myślę, że zbytnio nas to poruszyło, próbując wykorzystać nasze obiekty istoty, aby zrobić wszystko. – MartinR

+2

Inną kwestią związaną z rozwiązaniem 1 jest buforowanie: kontekst spowoduje buforowanie elementów, więc dane staną się nieaktualne, gdy zostaną zmodyfikowane poza uruchomionym wystąpieniem aplikacji. – Cocowalla

+0

Podany link rzucił wiele światła na sposób działania EF. Wielkie dzięki. – Brandon

0

Ty nie chcesz długo istniejącego kontekstu. Idealnie powinny być one na całe życie operacji żądania/danych.

Kiedy miałem do czynienia z podobnymi problemami, zakończyłem implementację repozytorium, które buforowało jednostki PK dla danego typu i zezwalało na "LoadFromDetached", które znajdowałoby jednostkę w bazie danych, i "kopiowało" wszystkie własności skalarne oprócz PK do nowo przyłączonego podmiotu.

Wydajność będzie troche dotkliwa, ale zapewnia kuloodporny sposób na upewnienie się, że właściwości nawigacji nie zostaną oszpecone przez "zapomnienie" o nich.

+0

Niedawno wpadłem na podobny typ problemu i skończyło się na tym, że znacznie poprawiłem wydajność. Wpisane w niewłaściwe miejsce, zobacz moją odpowiedź – Otake

0

Od pewnego czasu pojawiało się pytanie, ale ostatnio wpadłem na podobny problem i skończyło się na tym, co pomogło nam spełnić kryteria wydajności.

Po prostu dzielisz swoją listę na porcje i przetwarzasz je w wątki sperate w sposób wielowątkowy. Każdy nowy wątek inicjuje także własne uow, które wymaga dołączenia twoich bytów.

Należy pamiętać o tym, że baza danych musi być włączona do izolacji migawek; w przeciwnym razie możesz skończyć z zakleszczeniami. Musisz zadecydować, czy jest to prawidłowe dla wykonywanej operacji i związanego z nią procesu biznesowego. W naszym przypadku była to prosta aktualizacja jednostki produktu.

Prawdopodobnie trzeba będzie wykonać kilka testów, aby zadecydować o najlepszej wielkości porcji, a także ograniczyć równoległość, więc zawsze istnieje zasób, aby zakończyć operację.

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

mam wydawać kilkanaście StackOverflow wątków dotyczących EF i wielowątkowość. Wszystkie mają odpowiedzi wyjaśniające problem dogłębnie, ale tak naprawdę nie pokazują, jak to naprawić.

EF nie jest bezpieczna dla wątków, wszyscy już to wiemy. Ale z mojego doświadczenia wynika, że ​​jedynym ryzykiem są kreacje kontekstowe/manipulacje. W rzeczywistości jest to bardzo prosta poprawka, dzięki której można zachować leniwy załadunek.

Powiedzmy, że masz aplikację WPF i witrynę MVC. gdzie aplikacja WPF używa technologii wielowątkowej. Po prostu wyrzucasz kontekst db w wielowątkowość i trzymasz go, gdy nie jest. Przykład strony internetowej MVC, kontekst zostanie automatycznie usunięty po przedstawieniu widoku.

W warstwie aplikacji WPF użyć tego:

ProductBLL productBLL = new ProductBLL(true); 

W warstwie aplikacji MVC użyć tego:

ProductBLL productBLL = new ProductBLL(); 

Jak produkt warstwa logiki biznesowej powinien wyglądać :

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... 
} 

Jak twoja warstwa logiki bazy danych powinien wyglądać następująco:

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

Właśnie miał projekt, w którym próbuje użyć EF z wielu gwintowania spowodowało błędy.

Próbowałem

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

ale po prostu zmienił rodzaj błędu z błędem dataReader do wielu błędów gwintu.

Prostym rozwiązaniem było wykorzystanie procedur przechowywanych EF importu funkcyjnych

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

Kluczem jest, aby przejść do źródła danych i uniknąć modelu danych.