동시성 충돌 처리(EF6)
낙관적 동시성은 엔터티가 로드된 이후 데이터가 변경되지 않기를 바라며 엔터티를 데이터베이스에 저장하려고 낙관적으로 시도하는 것을 포함합니다. 데이터가 변경된 것으로 확인되면 예외가 throw되고 저장을 다시 시도하기 전에 충돌을 해결해야 합니다. 이 항목에서는 Entity Framework에서 이러한 예외를 처리하는 방법을 설명합니다. 이 토픽에서 설명하는 방법은 Code First 및 EF 디자이너를 사용하여 만든 모델에 동일하게 적용됩니다.
이 게시물은 낙관적 동시성에 대해 심도 있게 다루기에 적합하지 않습니다. 아래 섹션에서는 동시성 해결에 대한 몇 가지 지식을 가정하고 일반적인 작업에 대한 패턴을 보여 줍니다.
이러한 패턴 중 다수는 속성 값 작업에서 다룬 항목을 활용합니다.
독립 연결(외래 키가 엔터티의 속성에 매핑되지 않음)을 사용할 때 동시성 문제를 해결하는 것은 외래 키 연결을 사용하는 경우보다 훨씬 더 어렵습니다. 따라서 애플리케이션에서 동시성 해결을 수행하려면 항상 외래 키를 엔터티에 매핑하는 것이 좋습니다. 아래의 모든 예제에서는 외래 키 연결을 사용한다고 가정합니다.
외래 키 연결을 사용하는 엔터티를 저장하려고 시도하는 동안 낙관적 동시성 예외가 검색되면 SaveChanges에서 DbUpdateConcurrencyException을 throw합니다.
Reload를 사용하여 낙관적 동시성 예외 해결(데이터베이스 우선)
Reload 메서드를 사용하여 엔터티의 현재 값을 데이터베이스에 있는 값으로 덮어쓸 수 있습니다. 그런 다음 엔터티는 일반적으로 어떤 형태로든 사용자에게 다시 제공되며 사용자는 다시 변경하고 다시 저장해야 합니다. 예시:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update the values of the entity that failed to save from the store
ex.Entries.Single().Reload();
}
} while (saveFailed);
}
동시성 예외를 시뮬레이션하는 좋은 방법은 SaveChanges 호출에서 중단점을 설정한 다음, SQL Server Management Studio 같은 다른 도구를 사용하여 데이터베이스에 저장되는 엔터티를 수정하는 것입니다. SaveChanges 앞에 줄을 삽입하여 SqlCommand를 통해 데이터베이스를 직접 업데이트할 수도 있습니다. 예시:
context.Database.SqlCommand(
"UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");
DbUpdateConcurrencyException의 Entries 메서드는 업데이트에 실패한 엔터티에 대한 DbEntityEntry 인스턴스를 반환합니다. (이 속성은 현재 동시성 문제에 대해 항상 단일 값을 반환합니다. 일반 업데이트 예외에 대해 여러 값을 반환할 수 있습니다.) 일부 상황에 대한 대안은 데이터베이스에서 다시 로드해야 할 수 있는 모든 엔터티에 대한 항목을 가져오고 각 엔터티에 대해 다시 로드를 호출하는 것입니다.
클라이언트 우선으로 낙관적 동시성 예외 해결
Reload를 사용하는 위 예제는 엔터티의 값을 데이터베이스의 값으로 덮어쓰므로 데이터베이스 우선 또는 저장소 우선이라고도 합니다. 경우에 따라 반대로 수행하여 데이터베이스의 값을 현재 엔터티의 값으로 덮어써야 할 수도 있습니다. 이를 클라이언트 우선이라고도 하며 현재 데이터베이스 값을 가져와서 이를 엔터티의 원래 값으로 설정하는 방법으로 수행할 수 있습니다. 현재 및 원래 값에 대한 자세한 내용은 속성 값 작업을 참조하세요. 예를 들어:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Update original values from the database
var entry = ex.Entries.Single();
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
}
} while (saveFailed);
}
낙관적 동시성 예외의 사용자 지정 해결
경우에 따라 현재 데이터베이스의 값을 엔터티의 값과 결합해야 할 수도 있습니다. 일반적으로 사용자 지정 논리 또는 사용자 상호 작용이 필요합니다. 예를 들어 현재 값, 데이터베이스의 값 및 확인된 값의 기본 집합이 포함된 양식을 사용자에게 제공할 수 있습니다. 그러면 사용자는 필요에 따라 확인된 값을 편집하고 이러한 확인된 값이 데이터베이스에 저장됩니다. 이 작업은 엔터티 항목의 CurrentValues 및 GetDatabaseValues에서 반환된 DbPropertyValues 개체를 사용하여 수행할 수 있습니다. 예시:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// 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();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);
// 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);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
DbPropertyValues databaseValues,
DbPropertyValues resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them edit the resolved values to get the correct resolution.
}
개체를 사용하여 낙관적 동시성 예외의 사용자 지정 해결
위 코드는 DbPropertyValues 인스턴스를 사용하여 현재, 데이터베이스 및 확인된 값을 전달합니다. 경우에 따라 엔터티 형식의 인스턴스를 사용하는 것이 더 간단할 수 있습니다. 이 작업은 DbPropertyValues의 ToObject 및 SetValues 메서드를 사용하여 수행할 수 있습니다. 예시:
using (var context = new BloggingContext())
{
var blog = context.Blogs.Find(1);
blog.Name = "The New ADO.NET Blog";
bool saveFailed;
do
{
saveFailed = false;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
// Get the current entity values and the values in the database
// as instances of the entity type
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
var databaseValuesAsBlog = (Blog)databaseValues.ToObject();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();
// Have the user choose what the resolved values should be
HaveUserResolveConcurrency((Blog)entry.Entity,
databaseValuesAsBlog,
resolvedValuesAsBlog);
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValuesAsBlog);
}
} while (saveFailed);
}
public void HaveUserResolveConcurrency(Blog entity,
Blog databaseValues,
Blog resolvedValues)
{
// Show the current, database, and resolved values to the user and have
// them update the resolved values to get the correct resolution.
}
.NET