Entity Framework의 SqlException-세션에서 실행중인 다른 스레드가 있으므로 새 트랜잭션이 허용되지 않습니다.


600

현재이 오류가 발생합니다.

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에없는 경우 삽입하십시오. 처음부터 재고를 완전히 가져와야합니다. 밤새 피드에 새로운 인벤토리가 들어오지 않는 한 아무 것도 발생하지 않은 후에 실행됩니다.

그래서 큰 질문-내가받는 트랜잭션 오류를 해결하는 방법은 무엇입니까? 루프를 통해 매번 컨텍스트를 삭제하고 다시 만들어야합니까 (나에게 이해가되지 않습니까)?


6
이것은 내가 본 것 중 가장 상세한 질문입니다.

9
아직 저장 프로 시저를 놓친 사람이 있습니까?
David

답변:


690

머리카락을 많이 빼낸 후 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;
    // ...
}

14
그래, 나도 두통을 일으켰 어 나는 문제를 발견했을 때 의자에서 거의 떨어졌다! 문제의 기술적 원인을 이해하지만 이는 직관적이지 않으며 개발자가 "성공" 블로그 (blogs.msdn.com/brada/archive/2003/10/02/50420)
닥터 존스

9
대규모 데이터 세트의 성능이 그렇게 나쁘지 않습니까? 테이블에 수백만 개의 레코드가있는 경우 ToList ()는 그것들을 모두 메모리로 빨아들입니다. 나는이 문제에 처해 있었고 다음이 실현 가능한지 궁금해했다 .a) 엔티티 분리 b) 새로운 ObjectContext를 생성하고 분리 된 엔티티를 첨부하십시오. c) 새 ObjectContext에서 SaveChanges ()를 호출합니다. d) 새 ObjectContext에서 엔티티를 분리합니다. e) 이전 ObjectContext에 다시 연결합니다.
Abhijeet Patel

149
문제는 SaveChanges여전히 DB에서 결과를 가져 오는 동안 전화를 걸 수 없다는 것 입니다. 따라서 다른 솔루션은 루프가 완료되면 변경 사항을 저장하는 것입니다.
Drew Noakes

4
물린 후에도이 코드 를 Microsoft Connect에 추가했습니다. connect.microsoft.com/VisualStudio/feedback/details/612369/… 자유롭게 투표하십시오.
Ian Mercer

36
우리 개발자들은 결과에 대해 생각하지 않고 LINQ 쿼리에 .ToList ()를 추가하는 경향이 있습니다. .ToList ()를 추가하면 처음으로 유용합니다!
Marc

267

이미 식별 했으므로 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

17
감사. 귀하의 설명은 "답변"으로 표시된 것보다 훨씬 유용했습니다.
바그너 다 실바

1
대단하다. 한 가지 : 열에 대해 쿼리하고 해당 열의 값을 업데이트하는 경우 chunkNumber ++에주의해야합니다. . "ModifiedDate"열이 있고 .Where (x => x.ModifiedDate! = null)을 쿼리하고 있고 foreach 끝에 ModifiedDate에 대한 값을 설정했다고 가정 해 봅시다. 이 방법으로 레코드의 절반이 건너 뛰기 때문에 레코드의 절반을 반복하지 않습니다.
Arvand

불행히도 거대한 데이터 세트의 경우 OutofMemoryException이 발생 합니다. 메모리 예외 에서 엔티티 프레임 워크 큰 데이터 세트의 설명을 참조하십시오 . Entity Framework
Michael Freidgeim

나는 이것이 효과가 있다고 생각한다. var skip = 0; const int take = 100; <직원> emps 목록; while ((emps = db.Employees.Skip (.skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// 여기서 할 일}}이 답변을 공식화 하겠지만 아래 답변 목록 아래에 묻히고이 질문과 관련이 있습니다.
jwize

123

이제 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();
    }
} 

6
Transaction 라우트를 사용하는 경우 TransactionScope를 던지면 해결되지 않을 수 있습니다. 예를 들어 코드 작성을 대화식으로 디버깅하는 경우 시간이 오래 걸리는 경우 시간 제한을 연장하는 것을 잊지 마십시오. DB 호출. 다음은 트랜잭션 시간 제한을 1 시간으로 확장하는 코드입니다. using (var transaction = new TransactionScope (TransactionScopeOption.Required, new TimeSpan (1, 0, 0)))
Chris Moschini 2016

처음으로 "자습서 경로"에서 실제 예제로 이탈 한 경우이 오류가 발생했습니다. 그러나 나를 위해, 더 간단한 해결책, ITERATION ATERATION 후에 더 좋습니다! (이 경우에는 99 %의 시간이 필요하고 루프 내에서 1 % 만 데이터베이스를 저장해야합니다.)
spiderman

심한. 나는이 오류에 부딪쳤다. 매우 불쾌합니다. 두 번째 제안은 SaveChanges를 루프로 옮기는 것과 함께 저에게 매력처럼 작용했습니다. 루프 외부에서 변경 사항을 저장하는 것이 배치 변경에 더 적합하다고 생각했습니다. 그러나 좋습니다. 아닌 것 같아요?! :(
Mr. Young

.NET 4.5에서는 작동하지 않았습니다. TransactionScope를 사용할 때 "기본 공급자가 EnlistTransaction에서 실패했습니다." "라는 오류가 발생했습니다. 파트너 트랜잭션 관리자가 원격 / 네트워크 트랜잭션에 대한 지원을 비활성화했습니다. (HRESULT 예외 : 0x8004D025) "}". 반복 외부에서 작업을 수행합니다.
Diganta Kumar

전체 트랜잭션 시간 동안 테이블이 잠겨 있으므로 TransactionScope를 사용하는 것은 위험합니다.
Michael Freidgeim 2016 년

19

실제로 foreachEntity Framework를 사용하여 C # 의 루프 내에서 변경 내용을 저장할 수 없습니다 .

context.SaveChanges() 메소드는 일반 데이터베이스 시스템 (RDMS)에서 커미트처럼 작동합니다.

모든 변경 사항 (Entity Framework가 캐시)을 변경 한 다음 SaveChanges()데이터베이스 커밋 명령과 같이 루프 이후 (외부)를 호출하면 모든 변경 사항을 한 번에 저장 하면됩니다.

모든 변경 사항을 한 번에 저장할 수있는 경우 작동합니다.


2
"일반 데이터베이스 시스템 (RDMS)"을 보는 것이 재미 있다고 생각했습니다
Dinerdo

1
EF 컨텍스트의 90 %에서 SaveChanges를 반복해서 호출하는 것이 좋기 때문에 이것은 잘못된 것 같습니다.
Pxtl

foreach 루프가 db 엔티티를 반복하지 않는 한 SaveChanges를 반복적으로 호출하는 것이 좋습니다.
kerbasaurus

1
아하! for-each 루프 안에 컨텍스트를 가져옵니다! (pffft .. 내가 무슨 생각을 했니? ..) 감사합니다!
Adam Cox

18

context.SaveChanges()당신의 foreach(루프)의 끝을 놓아 두십시오 .


이것은 foreach 내부 저장으로 인해 내 경우에 더 나은 옵션입니다
Almeida

2
항상 옵션은 아닙니다.
Pxtl

9

항상 선택 사항을 목록으로 사용

예 :

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();

             }

1
이것은 좋은 습관이 아닙니다. 필요하지 않은 경우 자주 SaveChanges를 실행하지 말아야하며 "항상 선택 항목을 목록으로 사용하십시오"
Dinerdo

@ Dinerdo 실제로 시나리오에 따라 다릅니다. 제 경우에는 2 개의 foreach 루프가 있습니다. 외부 쿼리는 db 쿼리를 목록으로 사용했습니다. 예를 들어,이 foreach는 하드웨어 장치를 통과합니다. 내부 foreach는 각 장치에서 여러 데이터를 검색합니다. 요구 사항에 따라 각 장치에서 하나씩 데이터를 검색 한 후 데이터베이스에 데이터를 저장해야합니다. 프로세스가 끝날 때 모든 데이터를 저장하는 옵션은 아닙니다. 나는 같은 오류가 발생했지만 mzonerz의 솔루션이 효과가있었습니다.
jstuardo 12

@jstuardo 일괄 처리도 가능합니까?
Dinerdo

@ Dinerdo 나는 철학적 수준에서 좋은 습관이 아니라는 데 동의합니다. 그러나 for 루프 내부에서 코드가 다른 메서드 (AddToLog () 메서드라고 함)를 호출하는 몇 가지 상황이 존재하는데 여기에는 로컬로 db.SaveChanges ()에 대한 호출이 포함됩니다. 이 상황에서는 실제로 db.Save Changes에 대한 호출을 제어 할 수 없습니다. 이 경우 Tozone () 또는 유사한 구조체를 사용하면 mzonerz가 제안한대로 작동합니다. 감사!
A. Varma

실제로, 이것은 도움이 될 것보다 더 많은 상처를 줄 것입니다. 내가 말한 것을 견딜 수 있습니다-ToList ()는 항상 사용해서는 안되며, 모든 단일 항목 후에 변경 사항을 저장하는 것은 고성능 앱에서 가능한 한 피해야 할 것입니다. 이것은 임시 수정 IMO입니다. 어떤 로깅 방법을 사용하더라도 버퍼링을 이상적으로 활용해야합니다.
Dinerdo

8

참고 : 책과 일부 줄은 여전히 ​​유효하기 때문에 조정되었습니다.

SaveChanges () 메서드를 호출하면 반복이 완료되기 전에 예외가 발생하면 데이터베이스에 유지 된 모든 변경 내용을 자동으로 롤백하는 트랜잭션이 시작됩니다. 그렇지 않으면 트랜잭션이 커밋됩니다. 특히 많은 수의 엔티티를 업데이트하거나 삭제할 때 반복이 완료된 후가 아니라 각 엔티티 업데이트 또는 삭제 후에 메소드를 적용하려는 유혹이있을 수 있습니다.

모든 데이터가 처리되기 전에 SaveChanges ()를 호출하려고하면 "세션에서 실행중인 다른 스레드가 있으므로 새 트랜잭션이 허용되지 않습니다"예외가 발생합니다. 연결 문자열에서 MARS (Multiple Active Record Sets)를 활성화 한 경우에도 SQL Server에서 SqlDataReader가 열려있는 연결에서 새 트랜잭션을 시작할 수 없기 때문에 예외가 발생합니다 (EF의 기본 연결 문자열은 MARS를 활성화 함)

왜 일이 일어나고 있는지 이해하는 것이 더 좋습니다. ;-)


1
이것을 피하는 좋은 방법은 리더를 열어 두 번째 리더를 열고 해당 리더를 두 번째 리더에 넣었을 때입니다. 이것은 엔티티 프레임 워크에서 마스터 / 세부 사항을 업데이트 할 때 필요할 수 있습니다. 마스터 레코드의 첫 번째 연결과 세부 레코드의 두 번째 연결을 엽니 다. 읽기만하는 경우 문제가 없어야합니다. 업데이트하는 동안 문제가 발생합니다.
허먼 반 데르 블롬

유용한 설명. 당신이 옳습니다, 왜 일이 일어나고 있는지 이해하는 것이 좋습니다.
Dov Miller


5

나는이 같은 문제를 겪고 있었지만 다른 상황에 처해있다. 목록 상자에 항목 목록이 있습니다. 사용자가 항목을 클릭하고 삭제를 선택할 수 있지만 저장된 proc을 사용하여 항목을 삭제하는 데 많은 논리가 있기 때문에 항목을 삭제하고 있습니다. 저장된 proc을 호출하면 삭제가 정상적으로 작동하지만 나중에 SaveChanges를 호출하면 오류가 발생합니다. 내 솔루션은 EF 외부에서 저장 프로 시저를 호출하는 것이 었으며 정상적으로 작동했습니다. 어떤 이유로 든 EF 방식으로 저장 프로 시저를 호출하면 무언가가 열려 있습니다.


3
최근 비슷한 문제가 발생했습니다. 내 경우에는 SELECT빈 결과 집합을 생성하는 저장 프로 시저의 문 이 있었으며 해당 결과 집합을 읽지 않으면 SaveChanges해당 예외가 발생했습니다.
n0rd

SP의 읽지 않은 결과와 같은 것, 힌트를 많이 주셔서 감사합니다)
Pavel K

4

다음은 각 루프마다 SaveChanges ()를 호출 할 수있는 두 가지 옵션입니다.

첫 번째 옵션은 하나의 DBContext를 사용하여 반복 할 목록 객체를 생성 한 다음 SaveChanges ()를 호출 할 두 번째 DBContext를 작성하는 것입니다. 예를 들면 다음과 같습니다.

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

두 번째 옵션은 DBContext에서 데이터베이스 객체 목록을 가져 오지만 ID 만 선택하는 것입니다. 그런 다음 id 목록 (아마도 int)을 반복하고 각 int에 해당하는 객체를 가져 와서 그런 식으로 SaveChanges ()를 호출하십시오. 이 방법의 아이디어는 큰 정수 목록을 가져 오는 것입니다. 큰 db 객체 목록을 가져와 전체 객체에서 .ToList ()를 호출하는 것보다 훨씬 효율적입니다. 이 방법의 예는 다음과 같습니다.

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}

이것은 내가 생각하고 행한 훌륭한 대안이지만, 이것은 상향 조정되어야합니다. 참고 : i) 매우 큰 세트에 적합한 열거 가능한 것으로 반복 할 수 있습니다. ii) NoTracking 명령을 사용하여 너무 많은 레코드를로드 할 때 발생하는 문제를 피할 수 있습니다 (시나리오 인 경우). iii) 나는 기본 키 전용 옵션을 정말로 좋아합니다. 메모리에 훨씬 적은 양의 데이터를로드하기 때문에 매우 현명합니다. 그러나 잠재적으로 역동적 인 기본 데이터 세트에서 테이크 / 스킵을 다루지 않습니다.
Todd

4

foreach로 인해이 오류가 발생하고 루프에서 먼저 하나의 엔티티를 저장하고 루프에서 생성 된 ID를 더 사용해야하는 경우 가장 쉬운 해결책은 다른 DBContext를 사용하여 ID를 반환하고 사용할 엔티티를 삽입하는 것입니다 외부 맥락에서이 ID

예를 들어

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }

2

그래서 프로젝트에서 문제가 없었 foreach거나 .toList()실제로 우리가 사용한 AutoFac 구성에있는 것과 똑같은 문제가있었습니다 . 이로 인해 위의 오류가 발생했지만 다른 동등한 오류가 발생하는 이상한 상황이 발생했습니다.

이것은 우리의 수정 사항입니다.

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

에:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();

문제가 무엇이라고 생각하는지 자세히 설명해 주시겠습니까? 매번 새로운 Dbcontext를 생성하여이 문제를 해결 했습니까?
eran otzap

2

나는 그것이 오래된 질문이라는 것을 알고 있지만 오늘이 오류에 직면했습니다.

데이터베이스 테이블 트리거에 오류가 발생하면이 오류가 발생할 수 있음을 알았습니다.

이 오류가 발생하면 테이블 트리거도 확인할 수 있습니다.


2

거대한 ResultSet을 읽고 테이블의 일부 레코드를 업데이트해야했습니다. Drew Noakes답변 에서 제안한대로 청크를 사용하려고했습니다 .

불행히도 50000 레코드 후에 OutofMemoryException이 발생했습니다. 메모리 부족 예외에 대한 답변 엔터티 프레임 워크 큰 데이터 세트 는 다음과 같이 설명합니다.

EF는 변경 감지에 사용되는 데이터베이스의 두 번째 사본을 작성합니다 (데이터베이스에 대한 변경 사항을 유지할 수 있도록). EF는 컨텍스트의 수명 동안이 두 번째 세트를 보유하며이 세트는 메모리 부족을 유발합니다.

각 배치에 대한 컨텍스트를 다시 작성하는 것이 좋습니다.

따라서 기본 키의 최소값과 최대 값을 검색했습니다. 테이블에는 기본 키가 자동 증분 정수로 사용됩니다. 그런 다음 각 청크에 대한 컨텍스트를 열어 데이터베이스 레코드 청크에서 검색했습니다. 청크 컨텍스트를 처리 한 후 메모리가 닫히고 해제됩니다. 메모리 사용량이 늘어나지 않도록합니다.

아래는 내 코드의 스 니펫입니다.

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange 는 From 및 To 속성이있는 간단한 구조입니다.


나는 당신이 당신의 상황을 어떻게 "새롭게"하는지를 보지 못했습니다. 각 청크에 대해 단순히 새로운 컨텍스트를 만드는 것처럼 보입니다.
Suncat2000

@ Suncat2000, 당신 말이 맞아, 문맥은 짧은 수명의 객체 여야한다 stackoverflow.com/questions/43474112/…
Michael Freidgeim

1

나는 또한 같은 문제에 직면했다.

원인과 해결책은 다음과 같습니다.

http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx

삽입, 업데이트와 같은 데이터 조작 명령을 실행하기 전에 이전의 모든 활성 SQL 리더를 닫았는지 확인하십시오.

가장 일반적인 오류는 db에서 데이터를 읽고 값을 반환하는 함수입니다. 예를 들어 isRecordExist와 같은 기능.

이 경우 레코드를 찾은 후 독자를 닫는 것을 잊어 버린 경우 즉시 함수에서 돌아옵니다.


7
Entity Framework에서 "독자 닫기"란 무엇입니까? var result = myDb.Customers customer의 customer from customer.Id == customerId select customer와 같은 쿼리에는 가시적 인 판독기가 없습니다. 결과 반환. FirstOrDefault ();
Anthony

@Anthony 다른 답변에서 알 수 있듯이 EF를 사용하여 LINQ 쿼리 (IQueryable)를 열거하면 마지막 행이 반복 될 때까지 기본 DataReader가 열린 상태로 유지됩니다. 그러나 MARS가 연결 문자열에서 활성화하는 중요한 기능이지만 OP의 문제는 여전히 MARS만으로는 해결되지 않습니다. 기본 DataReader가 여전히 열려있는 동안 문제가 SaveChanges를 시도하고 있습니다.
Todd

1

아래 코드는 저에게 효과적입니다.

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}

2
SO에 오신 것을 환영합니다! 이것이 왜 효과가 있는지 설명 및 / 또는 링크 추가를 고려하십시오 . 코드 전용 답변은 일반적으로 SO의 품질이 좋지 않은 것으로 간주됩니다.
codeMagic

1

필자의 경우 EF를 통해 저장 프로 시저를 호출 한 다음 나중에 SaveChanges 에서이 예외가 발생했을 때 문제가 발생했습니다. 문제는 절차를 호출하는 데 있었고 열거자는 폐기되지 않았습니다. 다음과 같은 방법으로 코드를 수정했습니다.

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}

1

우리는이 오류를보기 시작 "세션에서 실행중인 다른 스레드가 있기 때문에 새로운 트랜잭션은 허용되지 않습니다"EF5에서 EF6으로 마이그레이션 한 후 .

구글은 우리를 여기로 데려 왔지만 우리는 SaveChanges()루프 안에서 전화하지 않았다 . DB에서 foreach 루프 읽기 내에서 ObjectContext.ExecuteFunction을 사용하여 저장 프로 시저를 실행할 때 오류가 발생했습니다.

ObjectContext.ExecuteFunction을 호출하면 함수가 트랜잭션으로 래핑됩니다. 이미 열린 리더가있는 동안 트랜잭션을 시작하면 오류가 발생합니다.

다음 옵션을 설정하여 트랜잭션에서 SP 랩핑을 사용하지 않을 수 있습니다.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

EnsureTransactionsForFunctionsAndCommands옵션을 사용하면 자체 트랜잭션을 만들지 않고 SP를 실행할 수 있으며 더 이상 오류가 발생하지 않습니다.

DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands 속성


0

나는 파티에 훨씬 늦었지만 오늘도 같은 오류에 직면했으며 어떻게 해결했는지 간단했습니다. 내 시나리오는 중첩 된 for-each 루프 내에서 DB 트랜잭션을 수행하는이 주어진 코드와 비슷했습니다.

문제는 단일 DB 트랜잭션이 for-each 루프보다 약간 시간이 오래 걸리기 때문에 이전 트랜잭션이 완료되지 않으면 새로운 트랙션에서 예외가 발생하므로 솔루션은 for-each 루프에서 새 객체를 만드는 것입니다 DB 트랜잭션을 만드는 곳.

위에서 언급 한 시나리오의 경우 솔루션은 다음과 같습니다.

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    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 ### //
                    }
                }

0

나는 조금 늦었지만이 오류도있었습니다. 값이 어디에서 업데이트되는지 확인하여 문제를 해결했습니다.

내 쿼리가 잘못되었고 250 개가 넘는 편집이 보류중인 것을 알았습니다. 그래서 쿼리를 수정했는데 이제 제대로 작동합니다.

내 상황에서 : 쿼리가 반환하는 결과를 디버깅하여 쿼리에서 오류를 확인하십시오. 그런 다음 쿼리를 수정하십시오.

이것이 미래의 문제를 해결하는 데 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.