현재이 오류가 발생합니다.
System.Data.SqlClient.SqlException : 세션에서 실행중인 다른 스레드가 있으므로 새 트랜잭션이 허용되지 않습니다.
이 코드를 실행하는 동안 :
public class ProductManager : IProductManager
{
#region Declare Models
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
#endregion
public IProduct GetProductById(Guid productId)
{
// Do a quick sync of the feeds...
SyncFeeds();
...
// get a product...
...
return product;
}
private void SyncFeeds()
{
bool found = false;
string feedSource = "AUTO";
switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
{
if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
{
var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
{
foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
{
if (targetProduct.alternateProductID == sourceProduct.AutoID)
{
found = true;
break;
}
}
if (!found)
{
var newProduct = new RivWorks.Model.Negotiation.Product();
newProduct.alternateProductID = sourceProduct.AutoID;
newProduct.isFromFeed = true;
newProduct.isDeleted = false;
newProduct.SKU = sourceProduct.StockNumber;
company.Product.Add(newProduct);
}
}
_dbRiv.SaveChanges(); // ### THIS BREAKS ### //
}
}
}
break;
}
}
}
모델 # 1-이 모델은 개발자 서버의 데이터베이스에 있습니다.
모델 # 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
모델 # 2-이 모델은 Prod Server의 데이터베이스에 있으며 매일 자동 피드로 업데이트됩니다. 대체 텍스트 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
참고 – 모델 # 1의 빨간색 원으로 표시된 항목은 모델 # 2에 “매핑”하는 데 사용되는 필드입니다. 모델 # 2의 빨간색 원은 무시하십시오. 즉, 다른 질문에 대한 답변입니다.
참고 : 여전히 클라이언트의 인벤토리에서 벗어난 경우 DB1에서 소프트 삭제를 수행 할 수 있도록 isDeleted 검사를 수행해야합니다.
이 특정 코드를 사용하여 DB1의 회사와 DB2의 클라이언트를 연결하고 DB2에서 제품 목록을 가져 와서 DB1에없는 경우 삽입하십시오. 처음부터 재고를 완전히 가져와야합니다. 밤새 피드에 새로운 인벤토리가 들어오지 않는 한 아무 것도 발생하지 않은 후에 실행됩니다.
그래서 큰 질문-내가받는 트랜잭션 오류를 해결하는 방법은 무엇입니까? 루프를 통해 매번 컨텍스트를 삭제하고 다시 만들어야합니까 (나에게 이해가되지 않습니까)?
답변
머리카락을 많이 빼낸 후 foreach
고리가 범인 이라는 것을 알았습니다 . 발생해야하는 것은 EF를 호출하지만 IList<T>
해당 대상 유형 으로 리턴 한 후IList<T>
입니다.
예:
IList<Client> clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
// ...
}
답변
이미 식별 했으므로 foreach
활성 판독기를 통해 데이터베이스에서 아직 드로잉중인 데이터를 저장할 수 없습니다 .
작은 데이터 세트의 경우 호출 ToList()
또는 문제 ToArray()
가 없지만 수천 개의 행이있는 경우 많은 양의 메모리를 사용하게됩니다.
행을 청크로로드하는 것이 좋습니다.
public static class EntityFrameworkUtil
{
public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
{
return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
}
public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
{
int chunkNumber = 0;
while (true)
{
var query = (chunkNumber == 0)
? queryable
: queryable.Skip(chunkNumber * chunkSize);
var chunk = query.Take(chunkSize).ToArray();
if (chunk.Length == 0)
yield break;
yield return chunk;
chunkNumber++;
}
}
}
위의 확장 방법이 주어지면 다음과 같이 쿼리를 작성할 수 있습니다.
foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
// do stuff
context.SaveChanges();
}
이 메소드를 호출 할 수있는 조회 가능 오브젝트를 주문해야합니다. 이는 Entity Framework IQueryable<T>.Skip(int)
가 순서가 지정된 쿼리에 대해서만 지원하기 때문에 서로 다른 범위에 대한 여러 쿼리가 순서가 안정적이어야한다는 것을 고려할 때 적합합니다. 순서가 중요하지 않은 경우 클러스터 된 인덱스를 가질 가능성이 있기 때문에 기본 키로 만 주문하십시오.
이 버전은 데이터베이스를 100 개씩 일괄 적으로 쿼리합니다. SaveChanges()
. 각 엔터티마다 호출됩니다.
처리량을 대폭 향상 시키려면 전화 SaveChanges()
횟수를 줄이십시오. 대신 다음과 같은 코드를 사용하십시오.
foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
foreach (var client in chunk)
{
// do stuff
}
context.SaveChanges();
}
결과적으로 데이터베이스 업데이트 호출이 100 배 줄어 듭니다. 물론 각 통화를 완료하는 데 시간이 오래 걸리지 만 결국에는 앞서 나갑니다. 마일리지가 다를 수 있지만 이것은 세계에서 더 빨랐습니다.
그리고 그것은 당신이보고있는 예외를 극복합니다.
편집하다 나는 SQL 프로파일 러를 실행 한 후이 질문을 다시 방문하고 성능을 향상시키기 위해 몇 가지 사항을 업데이트했습니다. 관심있는 사람이라면 여기 DB가 생성 한 내용을 보여주는 샘플 SQL이 있습니다.
첫 번째 루프는 아무것도 건너 뛸 필요가 없으므로 더 간단합니다.
SELECT TOP (100) -- the chunk size
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC
후속 호출은 이전 결과 덩어리를 건너 뛰어야하므로 다음 사용법을 소개합니다 row_number
.
SELECT TOP (100) -- the chunk size
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
FROM (
SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100 -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC
답변
이제 Connect에서 열린 버그 에 대한 공식 답변을 게시했습니다 . 권장되는 해결 방법은 다음과 같습니다.
이 오류는 Entity Framework가 SaveChanges () 호출 중에 암시 적 트랜잭션을 생성하기 때문에 발생합니다. 오류를 해결하는 가장 좋은 방법은 다른 패턴을 사용하거나 (읽는 동안 저장하지 않음) 트랜잭션을 명시 적으로 선언하는 것입니다. 가능한 세 가지 해결책은 다음과 같습니다.
// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
foreach (var person in context.People)
{
// Change to person
}
context.SaveChanges();
}
// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
using (var context = new MyContext())
{
foreach (var person in context.People)
{
// Change to person
context.SaveChanges();
}
}
transaction.Complete();
}
// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
var people = context.People.ToList(); // Note that this forces the database
// to evaluate the query immediately
// and could be very bad for large tables.
foreach (var person in people)
{
// Change to person
context.SaveChanges();
}
}
답변
실제로 foreach
Entity Framework를 사용하여 C # 의 루프 내에서 변경 내용을 저장할 수 없습니다 .
context.SaveChanges()
메소드는 일반 데이터베이스 시스템 (RDMS)에서 커미트처럼 작동합니다.
모든 변경 사항 (Entity Framework가 캐시)을 변경 한 다음 SaveChanges()
데이터베이스 커밋 명령과 같이 루프 이후 (외부)를 호출하면 모든 변경 사항을 한 번에 저장 하면됩니다.
모든 변경 사항을 한 번에 저장할 수있는 경우 작동합니다.
답변
context.SaveChanges()
당신의 foreach
(루프)의 끝을 놓아 두십시오 .
답변
항상 선택 사항을 목록으로 사용
예 :
var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();
그런 다음 변경 사항을 저장하면서 컬렉션을 반복합니다.
foreach (var item in tempGroupOfFiles)
{
var itemToUpdate = item;
if (itemToUpdate != null)
{
itemToUpdate.FileStatusID = 8;
itemToUpdate.LastModifiedDate = DateTime.Now;
}
Entities.SaveChanges();
}
답변
참고 : 책과 일부 줄은 여전히 유효하기 때문에 조정되었습니다.
SaveChanges () 메서드를 호출하면 반복이 완료되기 전에 예외가 발생하면 데이터베이스에 유지 된 모든 변경 내용을 자동으로 롤백하는 트랜잭션이 시작됩니다. 그렇지 않으면 트랜잭션이 커밋됩니다. 특히 많은 수의 엔티티를 업데이트하거나 삭제할 때 반복이 완료된 후가 아니라 각 엔티티 업데이트 또는 삭제 후에 메소드를 적용하려는 유혹이있을 수 있습니다.
모든 데이터가 처리되기 전에 SaveChanges ()를 호출하려고하면 “세션에서 실행중인 다른 스레드가 있으므로 새 트랜잭션이 허용되지 않습니다”예외가 발생합니다. 연결 문자열에서 MARS (Multiple Active Record Sets)를 활성화 한 경우에도 SQL Server에서 SqlDataReader가 열려있는 연결에서 새 트랜잭션을 시작할 수 없기 때문에 예외가 발생합니다 (EF의 기본 연결 문자열은 MARS를 활성화 함)
왜 일이 일어나고 있는지 이해하는 것이 더 좋습니다. 😉