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;
}
Czy obiekt ma atrybut [ConcurrencyCheck] dla niektórych właściwości? –
@ 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
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 –