2016-03-23 17 views
5

Używam metody EF5 i Data First do aktualizacji jednostek. Używam podejścia sugerowanego przez inne pytania do warunkowego aktualizowania tylko zmodyfikowanych właściwości w jednostkach.EF5 nie może obsłużyć Współbieżności podczas Aktualizowanie pól selektywnych

Oki, oto scenariusz Mój kontroler wywołuje Service z obiektami POCO i pobiera obiekty POCO z Service, warstwa Service rozmawia z warstwą danych, która wewnętrznie używa EF5 do pobierania encji z DB i Aktualizowania ich w DB.

Dane widoku są ładowane przez kontroler z obiektu DTO pobranego z warstwy usługi. Użytkownik wprowadza zmiany w widoku i przesyła dane JSON do kontrolera, który jest mapowany do obiektu DTO w kontrolerze (dzięki uprzejmości MVC). Sterownik wykonuje wywołanie warstwy Service za pomocą obiektu obiektu DTO (POCO). Usługa odwzorowuje obiekt POCO na obiekt encji EF i wywołuje metodę aktualizacji warstwy danych (tj. Repozytorium) w jednostce EF. W repozytorium pobieram istniejący obiekt z bazy danych i wywołuję metodę ApplyCurrentvaluesValues, a następnie sprawdzam, czy jakiekolwiek właściwości zostały zmodyfikowane. Po zmianie właściwości stosuję logikę niestandardową w innych jednostkach, które nie są powiązane z bieżącą encją, a także Aktualizuj "ZaktualizowanyAdminId" & "Data aktualizacji" bieżącego obiektu. Opublikuj to, nazywam metodę "SaveChanges" w Centext.

Każda rzecz, o której wspomniałem, działa poprawnie, chyba że wstawię punkt przerwania w wywołaniu "SaveChanges" i zaktualizuję pole zmodyfikowane przez użytkownika do innej wartości, a następnie "EFU nie wygeneruje" wyjątku DbUpdateConcurrencyException ". tj. Mogę uzyskać warunkową aktualizację & wywoływać niestandardową logikę, gdy właściwości moich zainteresowań zostaną zmodyfikowane, aby działały idealnie. Ale nie otrzymuję błędu w przypadku współbieżności, tzn. EF nie podnosi "DbUpdateConcurrencyException" w przypadku, gdy rekord jest aktualizowany pomiędzy mną, pobierając rekord z DB, aktualizując rekord i zapisując go.

W rzeczywistym scenariuszu jest uruchomiony cron offline, który sprawdza nowo utworzoną kampanię i tworzy dla nich portfel, i oznacza poniżej właściwość IsPortfolioCreated jako prawdę, w tym samym czasie użytkownik może edytować kampanię, a flagę można ustawić na false mimo że cron stworzył portfele.

Aby zreplikować scenariusz współbieżności, wstawiam punkt przerwania na zmiany SaveChanges, a następnie aktualizuję pole IsPortfolioCreated z menedżera korporacyjnego MS-Sql dla tej samej jednostki, ale wyjątek "wyjątek DbUpdateConcurrency" nie zostanie zgłoszony, mimo że dane w magazynie zostały zaktualizowane .

Oto mój kod odsyłającym

Public bool EditGeneralSettings(CampaignDefinition campaignDefinition) 
{ 
    var success = false; 
    //campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client 
    var updatedAdminId = campaignDefinition.UpdatedAdminId; 
    var updationDate = DateTime.UtcNow; 
    CmsContext context = null; 
    GlobalMasterContext globalMasterContext = null; 

    try 
    { 
     context = new CmsContext(SaveTimeout); 

     var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First(); 

     //Always use this fields from Server, no matter what comes from client 
     campaignDefinition.CreationDate = contextCampaign.CreationDate; 
     campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId; 
     campaignDefinition.UpdationDate = contextCampaign.UpdationDate; 
     campaignDefinition.AdminId = contextCampaign.AdminId; 
     campaignDefinition.AutoDecision = contextCampaign.AutoDecision; 
     campaignDefinition.CampaignCode = contextCampaign.CampaignCode; 
     campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated; 

     var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName; 

     // Will be used in the below if condition.... 
     var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower(); 
     var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower(); 

     //This also not firing concurreny exception.... 
     var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition); 
     ((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign); 
     var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition); 
     ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated); 
     var modifiedProperties = entry.GetModifiedProperties(); 

     //Even tried this , works fine but no Concurrency exception 
     //var entry = context.Entry(contextCampaign); 
     //entry.CurrentValues.SetValues(campaignDefinition); 
     //var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList(); 

     // If any fields modified then only set Updation fields 
     if (modifiedProperties.Count() > 0) 
     { 
      campaignDefinition.UpdatedAdminId = updatedAdminId; 
      campaignDefinition.UpdationDate = updationDate; 
      //entry.CurrentValues.SetValues(campaignDefinition); 
      updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition); 

      //Also perform some custom logic in other entities... Then call save changes 

      context.SaveChanges(); 

      //If campaign name changed call a SP in different DB.. 
      if (campaignNameChanged) 
      { 
       globalMasterContext = new GlobalMasterContext(SaveTimeout); 
       globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId); 
       globalMasterContext.SaveChanges(); 
      } 
     } 
     success = true; 
    } 
    catch (DbUpdateConcurrencyException ex) 
    { 
     //Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry 
     //In short Store Wins Strategy 

     //Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question... 

     // Get the current entity values and the values in the database 
     var entry = ex.Entries.Single(); 
     var currentValues = entry.CurrentValues; 
     var databaseValues = entry.GetDatabaseValues(); 

     // Choose an initial set of resolved values. In this case we 
     // make the default be the values currently in the database. 
     var resolvedValues = databaseValues.Clone(); 


     // Update the original values with the database values and 
     // the current values with whatever the user choose. 

     entry.OriginalValues.SetValues(databaseValues); 
     entry.CurrentValues.SetValues(resolvedValues); 

    } 
    catch (Exception ex) 
    { 
     if (ex.InnerException != null) 
      throw ex.InnerException; 
     throw; 
    } 
    finally 
    { 
     if (context != null) context.Dispose(); 
     if (globalMasterContext != null) globalMasterContext.Dispose(); 
    } 
    return success; 
} 
+0

Czy obiekt ma atrybut [ConcurrencyCheck] dla niektórych właściwości? –

+0

@ omar.ballerani atrybutu sprawdzania zgodności nie ma dla encji. Ale myślę, że powinien nadal działać tak, jak opisano w następującym artykule MSDN. https://msdn.microsoft.com/en-in/data/jj592904.aspx – Vipresh

+0

Dla mojego doświadczenia wyjątek DbUpdateConcurrencyException jest zgłaszany przez Entity Framework, gdy nie znajduje wiersza do aktualizacji. Po dodaniu atrybutu [ConcurrencyCheck] do jednej lub wielu właściwości, ef używa pokrewnej kolumny jako filtru dla zapytania aktualizacji –

Odpowiedz

2

Entity Framework nie robi nic szczególnego, dopóki nie współbieżności (jako programista) skonfigurować go, aby sprawdzić problemy współbieżności.

Próbujesz złapać DbUpdateConcurrencyException, dokumentację dla tego wyjątku mówi: „Wyjątek rzucony przez DbContext gdy oczekiwano, że SaveChanges dla podmiotu spowodowałoby aktualizacji bazy ale w rzeczywistości nie wiersze w bazie danych zostały naruszone. ", można go odczytać here

w bazie pierwszym podejściu, trzeba ustawić właściwość 'współbieżności Mode dla kolumny na«stałe»(domyślnie None).Spójrz na ten zrzut ekranu: enter image description here

Wersja kolumny to typ SQL SERVER TIMESTAMP, specjalny typ, który jest automatycznie aktualizowany przy każdym zmianie wiersza, przeczytaj o nim here.

Przy tej konfiguracji, można spróbować z tego prostego testu, czy wszystko działa zgodnie z oczekiwaniami:

try 
{ 
    using (var outerContext = new testEntities()) 
    { 
     var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1); 
     outerCust1.Description += "modified by outer context"; 
     using (var innerContext = new testEntities()) 
     { 
      var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1); 
      innerCust1.Description += "modified by inner context"; 
      innerContext.SaveChanges(); 
     } 
     outerContext.SaveChanges(); 
    } 
} 
catch (DbUpdateConcurrencyException ext) 
{ 
    Console.WriteLine(ext.Message); 
} 

W powyższym przykładzie aktualizacji od wewnętrznej kontekście zostaną popełnione, aktualizację z zewnętrznego kontekstu będzie generowany wyjątek DbUpdateConcurrencyException, ponieważ EF spróbuje zaktualizować encję, używając 2 kolumn jako filtrów: kolumny Id i wersji.

Mam nadzieję, że to pomoże!