엔티티 프레임 워크 :이 명령과 연관된 열린 DataReader가 이미 있습니다.


285

Entity Framework를 사용하고 있으며 때때로이 오류가 발생합니다.

EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

수동 연결 관리를 수행하지 않더라도.

이 오류는 간헐적으로 발생합니다.

오류를 유발하는 코드 (쉽게 읽기 위해 단축) :

        if (critera.FromDate > x) {
            t= _tEntitites.T.Where(predicate).ToList();
        }
        else {
            t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
        }

매번 새로운 연결을 열기 위해 Dispose 패턴을 사용합니다.

using (_tEntitites = new TEntities(GetEntityConnection())) {

    if (critera.FromDate > x) {
        t= _tEntitites.T.Where(predicate).ToList();
    }
    else {
        t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
    }

}

여전히 문제가있다

연결이 이미 열려있는 경우 EF가 연결을 재사용하지 않는 이유는 무엇입니까?


1
나는이 질문이 고대라는 것을 알고 있지만, 당신 predicatehistoricPredicate변수가 어떤 유형인지 알고 싶습니다 . 난 당신이 통과하면 것을 발견했습니다 Func<T, bool>Where()(그것을 않기 때문에 "여기서"메모리에) 가끔 작업을 컴파일합니다. 당신이 해야 일을 할 것은 통과 Expression<Func<T, bool>>Where().
James

답변:


351

연결을 끊는 것이 아닙니다. EF는 연결을 올바르게 관리합니다. 이 문제에 대한 나의 이해는 단일 연결에서 여러 개의 데이터 검색 명령 (또는 여러 개의 선택이있는 단일 명령)이 실행되고 다음 DataReader가 첫 번째 읽기가 완료되기 전에 실행된다는 것입니다. 예외를 피하는 유일한 방법은 여러 개의 중첩 된 DataReaders = MultipleActiveResultSets를 켜는 것입니다. 이 상황이 항상 발생하는 또 다른 시나리오는 쿼리 결과를 반복하고 (IQueryable) 반복 내에서로드 된 엔티티에 대한 지연로드를 트리거하는 것입니다.


2
말이 되겠네요 그러나 각 방법에는 하나의 선택 만 있습니다.
Sonic Soul

1
@Sonic : 질문입니다. 아마도 하나 이상의 명령이 실행되었지만 보이지 않습니다. 이것이 Profiler에서 추적 될 수 있는지 확실하지 않습니다 (두 번째 독자가 실행되기 전에 예외가 발생할 수 있음). 쿼리를 ObjectQuery로 캐스트하고 ToTraceString을 호출하여 SQL 명령을 볼 수도 있습니다. 추적하기가 어렵습니다. 나는 항상 화성을 켭니다.
Ladislav Mrnka

2
@Sonic : 실행 및 완료된 SQL 명령을 확인하려는 의도는 없습니다.
Ladislav Mrnka

11
내 문제는 두 번째 시나리오였습니다. '쿼리 결과를 반복 할 때 (IQueryable) 반복 내에서로드 된 엔티티에 대한 지연로드를 트리거합니다.'
Amr Elgarhy

6
MARS 활성화 하면 부작용이있을 수 있습니다 . designlimbo.com/?p=235
Søren Boisen

126

MARS (MultipleActiveResultSets)를 사용하는 대신 여러 결과 집합을 열지 않도록 코드를 작성할 수 있습니다.

당신이 할 수있는 일은 데이터를 메모리로 가져 오는 것입니다. 그러면 리더를 열지 않을 것입니다. 다른 결과 집합을 열려고하는 동안 결과 집합을 반복하여 발생하는 경우가 종종 있습니다.

샘플 코드 :

public class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogID { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostID { get; set; }
    public virtual Blog Blog { get; set; }
    public string Text { get; set; }
}

다음을 포함하는 데이터베이스에서 조회를 수행한다고 가정 해 보겠습니다.

var context = new MyContext();

//here we have one resultset
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5); 

foreach (var blog in largeBlogs) //we use the result set here
{
     //here we try to get another result set while we are still reading the above set.
    var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Important Text"));
}

다음 과 같이 .ToList () 를 추가하여 간단한 해결책을 찾을 수 있습니다 .

var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();

이것은 엔티티 프레임 워크가 목록을 메모리에로드하도록 강제하므로, foreach 루프에서 목록을 반복 할 때 더 이상 데이터 리더를 사용하여 목록을 열지 않고 대신 메모리에 있습니다.

예를 들어 일부 속성을 지연로드하려는 경우 이것이 바람직하지 않을 수 있음을 알고 있습니다. 이것은 주로이 문제가 어떻게 / 왜 발생하는지 설명하는 적절한 예이므로 그에 따라 결정을 내릴 수 있습니다.


7
이 솔루션은 저에게 효과적이었습니다. 쿼리 직후 및 결과로 다른 작업을 수행하기 전에 .ToList ()를 추가하십시오.
TJKjaer

9
이것을 조심하고 상식을 사용하십시오. 당신이 경우 ToList천 개체를 보내고, 메모리를 톤을 증가시키는 것입니다. 이 특정 예에서는 내부 쿼리를 첫 번째 쿼리와 결합하여 두 개가 아닌 하나의 쿼리 만 생성하는 것이 좋습니다.
kamranicus

4
@subkamran 내 요점은 바로 무언가에 대해 생각하고 상황에 맞는 것을 선택하는 것입니다. 예는 설명하기 위해 생각했던 임의의 것입니다.)
Jim Wolff

3
확실히, 나는 단지 복사 / 붙여 넣기 행복한 사람들을 위해 그것을 분명히 지적하고 싶었다 :)
kamranicus

날 쏘지 마라. 그러나 이것은 결코 문제에 대한 해결책이 아니다. "메모리의 데이터 풀링"은 언제 SQL 관련 문제에 대한 솔루션입니까? 데이터베이스와의 대화가 마음에 들기 때문에 "SQL 예외가 발생하기 때문에"메모리에서 무언가를 가져 오는 것을 선호하지 않습니다. 그럼에도 불구하고 코드가 제공되면 데이터베이스에 두 번 연락해야 할 이유가 없습니다. 한 번의 호출로 쉽게 수행 할 수 있습니다. 이와 같은 게시물에주의하십시오. ToList, First, Single, ... SQL 예외가 발생하는 경우가 아니라 메모리에 데이터가 필요한 경우에만 사용해야합니다 (따라서 원하는 데이터 만).
Frederik Prijck

70

이 문제를 극복하는 또 다른 방법이 있습니다. 더 나은 방법인지 여부는 상황에 따라 다릅니다.

이 문제는 게으른 로딩으로 인해 발생하므로 다음을 포함하여 지연 로딩을 피하는 것이 좋습니다.

var results = myContext.Customers
    .Include(x => x.Orders)
    .Include(x => x.Addresses)
    .Include(x => x.PaymentMethods);

적절한를 사용하면 IncludeMARS를 사용하지 않아도됩니다. 그러나 하나를 놓치면 오류가 발생하므로 MARS를 사용하는 것이 가장 쉬운 방법입니다.


1
매력처럼 일했다. .IncludeMARS를 사용하는 것보다 훨씬 나은 솔루션이며 자체 SQL 쿼리 코드를 작성하는 것보다 훨씬 쉽습니다.
Nolonar

15
람다가 아닌 .Include ( "string") 만 쓸 수있는 문제가있는 경우 확장 방법이 있기 때문에 "using System.Data.Entity"를 추가해야합니다.
Jim Wolff

46

반복하려는 컬렉션이 지연 로딩 (IQueriable) 인 경우이 오류가 발생합니다.

foreach (var user in _dbContext.Users)
{    
}

IQueriable 컬렉션을 다른 열거 가능한 컬렉션으로 변환하면이 문제가 해결됩니다. 예

_dbContext.Users.ToList()

참고 : .ToList ()는 매번 새 세트를 작성하므로 대용량 데이터를 처리하는 경우 성능 문제가 발생할 수 있습니다.


1
가장 쉬운 해결책! Big UP;)
Jacob Sobus

1
제한없는 목록을 가져 오면 심각한 성능 문제가 발생할 수 있습니다! 누구나 어떻게 그것을지지 할 수 있습니까?
SandRock

1
@SandRock은 소규모 회사에서 일하는 사람에게는 적합하지 않습니다- SELECT COUNT(*) FROM Users= 5
Simon_Weaver

5
그것에 대해 두 번 생각하십시오. 이 Q / A를 읽는 젊은 개발자는 이것이 항상 그렇지 않을 때 이것이 항상 해결책이라고 생각할 수 있습니다. 독자에게 db에서 무제한 목록을 가져올 위험에 대해 경고하기 위해 답을 편집하는 것이 좋습니다.
SandRock

1
@ SandRock 나는 이것이 당신이 모범 사례를 설명하는 답변이나 기사를 연결하기에 좋은 장소라고 생각합니다.
Sinjai

13

옵션을 생성자에 추가하여 문제를 쉽게 (실용적으로) 해결했습니다. 따라서 필요할 때만 사용합니다.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

2
감사합니다. 작동합니다. 방금 web.config에서 직접 연결 문자열에 MultipleActiveResultSets = true를 추가했습니다
Mosharaf Hossain

11

연결 문자열을 설정하여 시도하십시오 MultipleActiveResultSets=true. 데이터베이스에서 멀티 태스킹이 가능합니다.

Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

app.config의 연결 또는 프로그래밍 방식으로 설정했는지 여부에 관계없이 도움이됩니다.


연결 문자열에 MultipleActiveResultSets = true를 추가하면 문제가 해결 될 것입니다. 이 투표는하지 않아야합니다.
Aaron Hudon

네, 연결 문자열에 추가하는 방법을 보여
드렸습니다

4

원래 내 API 클래스에서 정적 필드를 사용하여 MyDataContext 객체 (MyDataContext가 EF5 Context 객체 인 경우)의 인스턴스를 참조하기로 결정했지만 문제가 발생한 것 같습니다. 모든 API 메소드에 다음과 같은 코드를 추가하여 문제를 해결했습니다.

using(MyDBContext db = new MyDBContext())
{
    //Do some linq queries
}

다른 사람들이 언급했듯이 EF 데이터 컨텍스트 개체는 스레드로부터 안전하지 않습니다. 따라서 정적 개체에 배치하면 올바른 조건에서 "데이터 판독기"오류가 발생합니다.

필자의 원래 가정은 개체의 인스턴스를 하나만 만드는 것이 더 효율적이고 더 나은 메모리 관리를 제공한다는 것입니다. 내가이 문제를 연구하면서 수집 한 것에서는 그렇지 않습니다. 실제로 API에 대한 각 호출을 격리 된 스레드 안전 이벤트로 처리하는 것이 더 효율적인 것 같습니다. 개체가 범위를 벗어날 때 모든 리소스가 올바르게 해제되도록합니다.

이는 API를 WebService 또는 REST API로 공개 할 다음 자연스럽게 진행하는 경우 특히 의미가 있습니다.

폭로

  • 운영체제 : Windows Server 2012
  • .NET : 4.0을 사용하여 4.5, Project 설치
  • 데이터 소스 : MySQL
  • 응용 프로그램 프레임 워크 : MVC3
  • 인증 : 양식

3

이 오류는 뷰에 IQueriable을 보내고 이중 foreach에서 사용할 때 발생합니다. 내부 foreach도 연결을 사용해야합니다. 간단한 예 (ViewBag.parents는 IQueriable 또는 DbSet 일 수 있음) :

foreach (var parent in ViewBag.parents)
{
    foreach (var child in parent.childs)
    {

    }
}

간단한 해결책은 .ToList()컬렉션을 사용하기 전에 사용하는 것입니다. 또한 MARS는 MySQL에서 작동하지 않습니다.


감사합니다! 여기에있는 모든 것은 "중첩 루프가 문제입니다"라고 말했지만 아무도 그것을 고치는 방법을 말하지 않았습니다. 나는를 넣어 ToList()DB를에서 컬렉션을 얻기 위해 내 최초의 호출시에. 그런 다음 foreach해당 목록을 작성하고 후속 호출이 오류를주는 대신 완벽하게 작동했습니다.
AlbatrossCafe

@AlbatrossCafe ...하지만 아무도 그런 경우에 데이터가 메모리에로드되고 쿼리가 DB 대신 메모리에서 실행된다는 언급은 없습니다.
Lightning3

3

나는이 같은 오류를 남겼, 나는이 사용되었을 때가 발생 Func<TEntity, bool>대신의 Expression<Func<TEntity, bool>>당신을 위해 predicate.

나는 모든을 변경하면 Func'sExpression's예외 슬로우 중단했다.

나는 그것이 단순히하지 않는 EntityFramwork영리한 일을 한다고 믿습니다.Expression'sFunc's


더 많은 투표가 필요합니다. (MyTParent model, Func<MyTChildren, bool> func)내 ViewModel이 whereGeneric DataContext 메소드에 특정 절을 지정할 수 있도록 DataContext 클래스에서 메소드를 작성하려고했습니다 . 내가 이것을 할 때까지 아무것도 작동하지 않았다.
Justin

3

이 문제를 완화하는 2 가지 솔루션 :

  1. .ToList()쿼리 후에 메모리 캐싱을 지연 로딩으로 유지 하여 새로운 DataReader를 열어 메모리 캐싱 을 반복 할 수 있습니다.
  2. .Include(/ 쿼리에로드하려는 추가 엔티티 /) 이것을 열망로드라고하며,이를 통해 DataReader로 쿼리를 실행하는 동안 관련 개체 (엔티티)를 포함시킬 수 있습니다.

2

MARS를 활성화하고 전체 결과 집합을 메모리로 검색하는 것 사이의 중간 정도는 초기 쿼리에서 ID 만 검색 한 다음 각 엔터티를 구체화하는 ID를 반복하는 것입니다.

예를 들어 ( 이 답변 에서처럼 "블로그 및 게시물"샘플 엔터티 사용 ) :

using (var context = new BlogContext())
{
    // Get the IDs of all the items to loop through. This is
    // materialized so that the data reader is closed by the
    // time we're looping through the list.
    var blogIds = context.Blogs.Select(blog => blog.Id).ToList();

    // This query represents all our items in their full glory,
    // but, items are only materialized one at a time as we
    // loop through them.
    var blogs =
        blogIds.Select(id => context.Blogs.First(blog => blog.Id == id));

    foreach (var blog in blogs)
    {
        this.DoSomethingWith(blog.Posts);

        context.SaveChanges();
    }
}

이렇게하면 수천 개의 전체 객체 그래프와 달리 단지 수천 개의 정수를 메모리로 가져 와서 메모리 사용을 최소화하면서 MARS를 활성화하지 않고 항목별로 작업 할 수 있습니다.

샘플에서 볼 수 있듯이 이것의 또 다른 장점은 루프 끝까지 기다릴 필요없이 각 항목을 반복하면서 변경 사항을 저장할 수 있다는 것입니다. MARS 사용 ( 여기여기 참조 )


context.SaveChanges();inside loop :(. 좋지 않습니다. 루프 외부에 있어야합니다.
Jawand Singh

1

내 경우에는 myContext.SaveChangesAsync () 호출 전에 "await"문이 누락 된 것을 발견했습니다. 비동기 호출 전에 대기를 추가하면 데이터 리더 문제가 해결되었습니다.


0

조건의 일부를 Func <> 또는 확장 메소드로 그룹화하려고하면이 오류가 발생합니다. 다음과 같은 코드가 있다고 가정하십시오.

public static Func<PriceList, bool> IsCurrent()
{
  return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
              (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

Or

public static IEnumerable<PriceList> IsCurrent(this IEnumerable<PriceList> prices) { .... }

Where ()에서 사용하려고하면 예외가 발생하지만 대신 다음과 같은 조건자를 작성해야합니다.

public static Expression<Func<PriceList, bool>> IsCurrent()
{
    return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
                (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

자세한 내용은 http://www.albahari.com/nutshell/predicatebuilder.aspx를 참조하십시오.


0

이 문제는 데이터를 목록으로 변환하여 간단히 해결할 수 있습니다.

 var details = _webcontext.products.ToList();


            if (details != null)
            {
                Parallel.ForEach(details, x =>
                {
                    Products obj = new Products();
                    obj.slno = x.slno;
                    obj.ProductName = x.ProductName;
                    obj.Price = Convert.ToInt32(x.Price);
                    li.Add(obj);

                });
                return li;
            }

ToList ()는 호출하지만 위의 코드는 여전히 연결을 삭제하지 않습니다. 그래서 당신의 _webcontext는 여전히 라인 1의 시점에서 닫힐 위험이 있습니다
Sonic Soul

0

내 상황에서 종속성 주입 등록으로 인해 문제가 발생했습니다. dbcontext를 사용하는 요청 범위 서비스를 단일 등록 서비스에 주입했습니다. 따라서 dbcontext가 여러 요청 내에서 사용되어 오류가 발생했습니다.


0

필자의 경우이 문제는 MARS 연결 문자열과 관련이 없지만 json 직렬화와 관련이 있습니다. NetCore2에서 3으로 프로젝트를 업그레이드 한 후이 오류가 발생했습니다.

자세한 내용은 여기를 참조하십시오


-6

두 번째 쿼리 전에 다음 코드 섹션을 사용하여이 문제를 해결했습니다.

 ...first query
 while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
 {
     System.Threading.Thread.Sleep(500);
 }
 ...second query

수면 시간을 밀리 초 단위로 변경할 수 있습니다

PD 스레드를 사용할 때 유용


13
어떤 솔루션에도 Thread.Sleep을 임의로 추가하는 것은 좋지 않습니다. 특히 일부 값의 상태를 완전히 이해하지 못하는 다른 문제를 피하기 위해 사용하는 경우 특히 나쁩니다. 응답의 맨 아래에 표시된 "스레드 사용"은 스레딩에 대한 기본적인 이해를 의미한다고 생각했지만이 응답에는 컨텍스트가 고려되지 않습니다. 특히 상황이 좋지 않은 상황 UI 스레드와 같이 Thread.Sleep을 사용합니다.
Mike Tours
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.