콘텐츠를로드하지 않고 EntityFramework 내에서 행을 계산하는 방법은 무엇입니까?


109

계산 방법을 결정하려고합니다EntityFramework를 사용하여 테이블에서 일치하는 행 하는 .

문제는 각 행에 이진 필드에 많은 메가 바이트의 데이터가있을 수 있다는 것입니다. 물론 SQL은 다음과 같습니다.

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';

모든 행을로드 한 다음 다음을 사용 하여 Count 찾을 수 있습니다.

var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();

그러나 그것은 매우 비효율적입니다. 더 간단한 방법이 있습니까?


편집 : 감사합니다, 모두. 프로파일 링을 실행할 수 있도록 연결된 개인에서 DB를 이동했습니다. 이것은 도움이되지만 내가 예상하지 못한 혼란을 야기합니다.

그리고 제 실제 데이터는 조금 더 깊습니다. 저는 트럭 을 사용 하여 아이템 케이스 팔레트 를 운반 할 것입니다. 적어도 하나의 아이템 이 없으면 트럭 이 떠나는 것을 원하지 않습니다 .

내 시도는 다음과 같습니다. 내가 이해하지 못하는 부분은 CASE_2가 DB 서버 (MSSQL)에 액세스하지 않는다는 것입니다.

var truck = context.Truck.FirstOrDefault(t => (t.ID == truckID));
if (truck == null)
    return "Invalid Truck ID: " + truckID;
var dlist = from t in ve.Truck
    where t.ID == truckID
    select t.Driver;
if (dlist.Count() == 0)
    return "No Driver for this Truck";

var plist = from t in ve.Truck where t.ID == truckID
    from r in t.Pallet select r;
if (plist.Count() == 0)
    return "No Pallets are in this Truck";
#if CASE_1
/// This works fine (using 'plist'):
var list1 = from r in plist
    from c in r.Case
    from i in c.Item
    select i;
if (list1.Count() == 0)
    return "No Items are in the Truck";
#endif

#if CASE_2
/// This never executes any SQL on the server.
var list2 = from r in truck.Pallet
        from c in r.Case
        from i in c.Item
        select i;
bool ok = (list.Count() > 0);
if (!ok)
    return "No Items are in the Truck";
#endif

#if CASE_3
/// Forced loading also works, as stated in the OP...
bool ok = false;
foreach (var pallet in truck.Pallet) {
    pallet.Case.Load();
    foreach (var kase in pallet.Case) {
        kase.Item.Load();
        var item = kase.Item.FirstOrDefault();
        if (item != null) {
            ok = true;
            break;
        }
    }
    if (ok) break;
}
if (!ok)
    return "No Items are in the Truck";
#endif

CASE_1의 결과 SQL은 sp_executesql을 통해 파이프 되지만 다음과 같습니다.

SELECT [Project1].[C1] AS [C1]
FROM   ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN  (SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT(cast(1 as bit)) AS [A1]
        FROM   [dbo].[PalletTruckMap] AS [Extent1]
        INNER JOIN [dbo].[PalletCaseMap] AS [Extent2] ON [Extent1].[PalletID] = [Extent2].[PalletID]
        INNER JOIN [dbo].[Item] AS [Extent3] ON [Extent2].[CaseID] = [Extent3].[CaseID]
        WHERE [Extent1].[TruckID] = '....'
    )  AS [GroupBy1] ) AS [Project1] ON 1 = 1

[ 나는 트럭, 운전사, 팔레트, 케이스 또는 품목이 실제로 없습니다. SQL에서 볼 수 있듯이 Truck-Pallet 및 Pallet-Case 관계는 다 대다 관계입니다. 비록 중요하지 않다고 생각합니다. 내 진짜 물건은 무형이고 설명하기가 더 어려워서 이름을 변경했습니다. ]


1
팔레트 적재 문제를 어떻게 해결 했습니까?
Sherlock

답변:


123

쿼리 구문 :

var count = (from o in context.MyContainer
             where o.ID == '1'
             from t in o.MyTable
             select t).Count();

방법 구문 :

var count = context.MyContainer
            .Where(o => o.ID == '1')
            .SelectMany(o => o.MyTable)
            .Count()

둘 다 동일한 SQL 쿼리를 생성합니다.


SelectMany()? 필요합니까? 그것 없이는 제대로 작동하지 않습니까?
Jo

@JoSmo, 아니, 완전히 다른 쿼리입니다.
Craig Stuntz

나를 위해 그것을 정리해 주셔서 감사합니다. 확실히하고 싶었을뿐입니다. :)
Jo Smo

1
SelectMany와 다른 이유를 말씀해 주시겠습니까? 이해가 안 돼요. SelectMany 없이도하지만 2 천만 개가 넘는 레코드가 있기 때문에 정말 느려집니다. 나는 Yang Zhang의 답변을 시도하고 훌륭하게 작동하며 SelectMany가 무엇을하는지 알고 싶었습니다.
mikesoft

1
@AustinFelipe SelectMany를 호출하지 않으면 쿼리는 ID가 '1'인 MyContainer의 행 수를 반환합니다. SelectMany 호출 (결과 의미 쿼리의 이전 결과에 속을 MyTable의 모든 행 반환 MyContainer.Where(o => o.ID == '1'))
sbecker

48

나는 당신이 뭔가를 원한다고 생각합니다

var count = context.MyTable.Count(t => t.MyContainer.ID == '1');

(댓글을 반영하도록 편집 됨)


1
아니요, 그는 MyContainer에서 ID = 1 인 하나의 엔티티가 참조하는 MyTable의 엔티티 수를 필요로합니다
Craig Stuntz

3
참고로 t.ID가 PK이면 위 코드의 개수는 항상 1 이됩니다 . :)
Craig Stuntz

2
@Craig, 당신 말이 맞아요, 나는 t.ForeignTable.ID를 사용해야했습니다. 업데이트되었습니다.
Kevin

1
이것은 짧고 간단합니다. 내 선택은 : var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); 길고 못 생겼습니다 : var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); 하지만 코딩 스타일에 따라 다릅니다 ...
CL

당신이 "System.Linq 사용", 또는이 실 거예요 작업을 포함 할
CountMurphy

16

내가 이해했듯이 선택한 답변은 여전히 ​​모든 관련 테스트를로드합니다. 이 msdn 블로그에 따르면 더 나은 방법이 있습니다.

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

구체적으로 특별히

using (var context = new UnicornsContext())

    var princess = context.Princesses.Find(1);

    // Count how many unicorns the princess owns 
    var unicornHaul = context.Entry(princess)
                      .Collection(p => p.Unicorns)
                      .Query()
                      .Count();
}

3
추가 Find(1)요청 을 할 필요가 없습니다 . 엔터티를 만들고 컨텍스트에 연결하면됩니다.var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits

13

이것은 내 코드입니다.

IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();

변수가 IQueryable로 정의되어 있는지 확인한 다음 Count () 메서드를 사용할 때 EF는 다음과 같은 것을 실행합니다.

select count(*) from ...

그렇지 않고 레코드가 IEnumerable로 정의 된 경우 생성 된 SQL은 전체 테이블을 쿼리하고 반환 된 행 수를 계산합니다.


10

음, SELECT COUNT(*) FROM TableSQL Server는 실제로는 아무것도 할 수 없지만 전체 테이블 스캔 (클러스터형 인덱스 스캔)을 수행하기 때문에 특히 큰 테이블에서는 상당히 비효율적입니다.

때로는 데이터베이스에서 대략적인 행 수를 아는 것으로 충분하며, 이러한 경우 다음과 같은 문으로 충분할 수 있습니다.

SELECT 
    SUM(used_page_count) * 8 AS SizeKB,
    SUM(row_count) AS [RowCount], 
    OBJECT_NAME(OBJECT_ID) AS TableName
FROM 
    sys.dm_db_partition_stats
WHERE 
    OBJECT_ID = OBJECT_ID('YourTableNameHere')
    AND (index_id = 0 OR index_id = 1)
GROUP BY 
    OBJECT_ID

이것은 동적 관리 뷰를 검사하고 특정 테이블이 주어지면 여기에서 행 수와 테이블 크기를 추출합니다. 힙 (index_id = 0) 또는 클러스터형 인덱스 (index_id = 1)에 대한 항목을 합산하여이를 수행합니다.

빠르고 사용하기 쉽지만 100 % 정확하거나 최신 상태라는 보장은 없습니다. 그러나 많은 경우에 이것은 "충분히 좋다"(그리고 서버에 훨씬 덜 부담을 준다).

그게 당신에게도 효과가 있을까요? 물론 EF에서이를 사용하려면 저장된 proc에이를 래핑하거나 직접적인 "SQL 쿼리 실행"호출을 사용해야합니다.

마크


1
WHERE의 FK 참조로 인해 전체 테이블 스캔이 아닙니다. 마스터의 세부 정보 만 스캔됩니다. 그가 겪었던 성능 문제는 레코드 수가 아니라 Blob 데이터를로드하는 것입니다. 일반적으로 마스터 레코드 당 수십만 개 이상의 세부 레코드가 없다고 가정하면 실제로 느리지 않은 것을 "최적화"하지 않을 것입니다.
Craig Stuntz

예,이 경우에는 하위 집합 만 선택합니다. 괜찮습니다. Blob 데이터의 경우-EF 테이블의 모든 열에 "지연된로드"를 설정하여로드를 방지 할 수 있다는 인상을 받았습니다.
marc_s

이 SQL을 EntityFramework와 함께 사용하는 방법이 있습니까? 어쨌든이 경우에는 일치하는 행이 있다는 것만 알면되었지만 의도적으로 더 일반적으로 질문했습니다.
NVRAM

4

엔터티 컨텍스트 의 ExecuteStoreQuery 메서드를 사용합니다 . 이렇게하면 전체 결과 집합을 다운로드하고 간단한 행 수를 계산하기 위해 개체로 역 직렬화하는 것을 방지 할 수 있습니다.

   int count;

    using (var db = new MyDatabase()){
      string sql = "SELECT COUNT(*) FROM MyTable where FkId = {0}";

      object[] myParams = {1};
      var cntQuery = db.ExecuteStoreQuery<int>(sql, myParams);

      count = cntQuery.First<int>();
    }

6
작성 int count = context.MyTable.Count(m => m.MyContainerID == '1')하면 생성 된 SQL이 수행중인 작업과 정확히 유사하지만 코드가 훨씬 더 좋습니다. 이와 같이 메모리에로드되는 엔티티는 없습니다. 원하는 경우 LINQPad에서 시도해보십시오. 내부에서 사용되는 SQL이 표시됩니다.
Drew Noakes 2012-08-04

인라인 SQL. . 내가 가장 좋아하는 것이 아닙니다.
Duanne 2017 년

3

이게 효과가있을 것 같아요 ...

var query = from m in context.MyTable
            where m.MyContainerId == '1' // or what ever the foreign key name is...
            select m;

var count = query.Count();

이것은 내가 처음에 갔던 방향이지만 수동으로 추가하지 않는 한 m에는 MyContainer 속성이 있지만 MyContainerId는 없다는 것을 이해합니다. 따라서 검사하려는 것은 m.MyContainer.ID입니다.
Kevin

MyContainer가 부모이고 MyTable이 관계의 자식 인 경우 일부 외래 키와 해당 관계를 설정해야했습니다. MyContainer 엔터티와 연결된 MyTable 엔터티를 어떻게 알 수 있는지 잘 모르겠습니다. 구조에 대한 가정을했습니다 ...
bytebender
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.