엔티티는 LINQ to Entities 조회에서 구성 할 수 없습니다


389

엔티티 프레임 워크에 의해 생성되는 product라는 엔티티 유형이 있습니다. 이 쿼리를 작성했습니다

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

아래 코드는 다음 오류를 발생시킵니다.

"엔터티 또는 복합 유형 Shop.Product는 LINQ to Entities 쿼리에서 구성 할 수 없습니다."

var products = productRepository.GetProducts(1).Tolist();

그러나 select p대신 대신 사용하면 select new Product { Name = p.Name};올바르게 작동합니다.

맞춤 선택 섹션을 미리 수행하려면 어떻게해야합니까?


System.NotSupportedException : '엔터티 또는 복합 유형'StudentInfoAjax.Models.Student '는 LINQ to Entities 쿼리에서 구성 할 수 없습니다.'
Md Wahid

답변:


390

매핑 된 엔터티에 프로젝트를 투사 할 수 없으며 사용할 수 없어야합니다. 그러나 익명 형식이나 DTO 에 투영 할 수 있습니다 .

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}

그리고 귀하의 방법은 DTO 목록을 반환합니다.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}

152
왜 내가 이것을 할 수 없어야하는지 모르겠다 ... 이것은 매우 유용 할 것이다 ...
Jonx

118
EF의 매핑 된 엔터티는 기본적으로 데이터베이스 테이블을 나타냅니다. 매핑 된 엔터티에 투영하는 경우 기본적으로 수행하는 작업은 유효한 상태가 아닌 엔터티를 부분적으로 로드하는 것입니다. EF는 미래에 그러한 엔티티의 업데이트를 처리하는 방법에 대한 단서가 없습니다 (기본 동작은로드되지 않은 필드를 null 또는 객체에있는 모든 항목으로 덮어 쓰는 것입니다). DB의 일부 데이터가 손실 될 위험이 있으므로 EF에서 엔터티 (또는 매핑 된 엔터티에 프로젝트)를 부분적으로로드 할 수 없으므로 위험한 작업입니다.
Yakimych

26
@Yakimych는 쿼리를 통해 생성 / 생성하는 집계 엔터티가 있으므로 조작하고 나중에 추가 할 새로운 엔터티를 완전히 인식 / 의도하는 경우를 제외하고는 의미가 있습니다. 이 경우 강제로 쿼리를 실행하거나 dto로 푸시 한 다음 엔터티로 다시 밀어 넣어야합니다.-실망스러운
Cargowire

16
@Cargowire-시나리오가 존재한다는 것에 동의하며, 현재하고있는 일을 알고 있지만 한계로 인해 할 수없는 경우에는 실망합니다. 그러나 이것이 허용되면 부분적으로로드 된 엔티티를 저장하려고 할 때 데이터 손실에 대해 불만을 갖는 많은 좌절 된 개발자가있을 것입니다. 많은 노이즈 (예외 발생 등)로 인해 발생하는 오류 인 IMO는 추적 및 설명하기 어려운 숨겨진 버그를 유발할 수있는 동작보다 낫습니다.
Yakimych


275

익명 유형으로 투영 한 다음 모델 유형으로 투영 할 수 있습니다.

public IEnumerable<Product> GetProducts(int categoryID)
{
    return (from p in Context.Set<Product>()
            where p.CategoryID == categoryID
            select new { Name = p.Name }).ToList()
           .Select(x => new Product { Name = x.Name });
}

편집 : 나는이 질문에 많은 관심을 가지고 있기 때문에 좀 더 구체적으로 할 것입니다.

모델 유형으로 직접 투사 할 수 없으므로 (EF 제한)이 문제를 해결할 방법이 없습니다. 유일한 방법은 익명 유형 (1 차 반복)으로 투영 한 다음 모델 유형 (2 차 반복)으로 투영하는 것입니다.

이러한 방식으로 엔티티를 부분적으로로드 할 때 업데이트 할 수 없으므로 분리 된 상태를 유지해야합니다.

왜 이것이 불가능한지 완전히 이해하지 못 했으며이 스레드의 대답은 그것에 대해 강력한 이유를주지 않습니다 (주로 부분적으로로드 된 데이터에 대해 말함). 부분적으로로드 된 상태 엔터티는 업데이트 할 수 없지만이 엔터티는 분리되므로 실수로 저장하려고 시도 할 수 없습니다.

위에서 사용한 방법을 고려하십시오. 결과적으로 부분적으로로드 된 모델 엔티티가 있습니다. 이 엔티티가 분리되었습니다.

다음과 같은 가능한 코드를 고려하십시오.

return (from p in Context.Set<Product>()
        where p.CategoryID == categoryID
        select new Product { Name = p.Name }).AsNoTracking().ToList();

이로 인해 분리 된 엔티티 목록이 생성 될 수 있으므로 두 번 반복 할 필요가 없습니다. 컴파일러는 AsNoTracking ()을 사용하여 엔티티가 분리되어이를 수행 할 수 있음을 알 수 있습니다. 그러나 AsNoTracking ()이 생략 된 경우 원하는 결과에 대해 구체적으로 지정해야한다는 경고를 표시하기 위해 현재와 동일한 예외가 발생할 수 있습니다.


3
이것은 투영하려는 선택된 엔티티의 상태를 필요로하지 않거나 상관하지 않을 때 가장 깨끗한 솔루션입니다.
Mário Meyrelles

2
그리고 IEnumerable 또는 IQueryable;)을 반환하면 신경 쓰지 않습니다. 그러나 여전히이 솔루션이 지금 저에게 효과적이라고 생각합니다.
Michael Brennt

10
기술적으로 모델 유형에 대한 투영은 쿼리 외부에서 발생하며 목록을 통해 추가 반복이 필요하다고 생각합니다. 내 코드에는이 솔루션을 사용하지 않지만 질문에 대한 솔루션입니다. uptick.
1c1cle

4
나는 훨씬 더 우아하고 깨끗한 DTO 솔루션을 선호한다
Adam Hey

7
그 점을 제외하고는 실제로 질문에 대한 답변이 아닙니다. 이것은 Linq to Entities 쿼리 프로젝션이 아니라 Linq To Objects 프로젝션을 수행하는 방법에 대한 답변입니다. 따라서 DTO 옵션은 유일한 옵션 인 Linq to Entities입니다.
rism

78

내가 찾은 또 다른 방법이 있습니다. 제품 클래스에서 파생되는 클래스를 작성하고 사용해야합니다. 예를 들어 :

public class PseudoProduct : Product { }

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new PseudoProduct() { Name = p.Name};
}

이것이 "허용"되는지 확실하지 않지만 작동합니다.


3
영리한! 지금 이것을 시도하고 작동합니다. 그래도 어떻게 든 화상을 입을 것이라고 확신합니다.
다니엘

5
EF가 PseudoProduct에 대한 매핑을 찾을 수 없기 때문에 GetProducts ()의 결과를 유지하려고하면 BTW가 문을 닫습니다 (예 : "System.InvalidOperationException : EntityType 'blah.PseudoProduct'에 대한 매핑 및 메타 데이터 정보를 찾을 수 없음").
sming dec

4
최상의 답변이며 질문의 매개 변수 내에서 답변하는 유일한 답변입니다. 다른 모든 답변은 반환 유형을 변경하거나 IQueryable을 조기에 실행하고 linq를 사용하여 객체
rdans

2
EF 6.1에서는 100 % 충격을 받았습니다.
TravisWhidden

2
@mejobloggs 파생 클래스에서 [NotMapped] 특성을 시도하거나 유창한 API를 사용하는 경우 .Ignore <T>를 시도하십시오.
Dunc

37

다음은 추가 클래스를 선언하지 않고이를 수행하는 한 가지 방법입니다.

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select new { Name = p.Name };
    var products = query.ToList().Select(r => new Product
    {
        Name = r.Name;
    }).ToList();

    return products;
}

그러나 여러 엔터티를 단일 엔터티에 결합하려는 경우에만 사용됩니다. 위의 기능 (간단한 제품 대 제품 매핑)은 다음과 같이 수행됩니다.

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select p;
    var products = query.ToList();

    return products;
}

23

또 다른 간단한 방법 :)

public IQueryable<Product> GetProducts(int categoryID)
{
    var productList = db.Products
        .Where(p => p.CategoryID == categoryID)
        .Select(item => 
            new Product
            {
                Name = item.Name
            })
        .ToList()
        .AsQueryable(); // actually it's not useful after "ToList()" :D

    return productList;
}

좋은 점은 방금 당신의 멋진 답변으로 IQueryable을 배웠습니다. 왜 ToList () 뒤에 유용하지 않은지 설명하고 싶다면 LINQ-to-SQL 쿼리에서 일반 목록을 사용할 수 없기 때문입니다. 따라서 항상 호출자가 결과를 다른 쿼리로 푸시한다는 것을 알고 있다면 IQueryable이 될 것입니다. 그러나 그렇지 않은 경우 ... 이후에 일반 목록으로 사용하려는 경우 메소드 내에서 ToList ()를 사용하여 IQueryable에서 ToList ()를 수행하지 않도록하십시오.
PositiveGuy

당신은 완전히 내 친구입니다. 나는 질문 방법 서명을 모방합니다. 왜냐하면 나는 그것을 쿼리 가능으로 변환하기 때문입니다 ...;)
Soren

1
이렇게하면 ToList () 다음에 productList를 편집 할 수 없게됩니다. 편집 가능하게하려면 어떻게해야합니까?
doncadavona

.ToList쿼리 를 넣으면 서버에서 데이터를 가져 와서 다시 가져 오는 것이 무엇 AsQueryable입니까?
Moshii

1
@Moshii는 메소드 반환 유형 서명을 만족시키기 위해 (답변에서 말했듯이 더 이상 유용하지 않습니다).
Soren

4

이것을 사용할 수 있고 작동해야합니다-> toListselect를 사용하여 새 목록을 만들기 전에 사용해야합니다 .

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 

3
그러나 이것은 여전히 ​​'SELECT name FROM [..]'이 아닌 'SELECT * FROM [..]'을 수행합니다.
Timo Hermans

1

중복으로 표시된 다른 질문 ( 여기 참조 )에 대한 응답으로 Soren의 답변을 기반으로 빠르고 쉬운 해결책을 찾았습니다.

data.Tasks.AddRange(
    data.Task.AsEnumerable().Select(t => new Task{
        creator_id   = t.ID,
        start_date   = t.Incident.DateOpened,
        end_date     = t.Incident.DateCLosed,
        product_code = t.Incident.ProductCode
        // so on...
    })
);
data.SaveChanges();

참고 :이 솔루션은 Task 클래스 (여기서는 'Incident'라고 함)에 탐색 속성 (외부 키)이있는 경우에만 작동합니다. 그것을 가지고 있지 않다면, "AsQueryable ()"과 함께 게시 된 다른 솔루션 중 하나를 사용할 수 있습니다.


1

DTO (Data Transfer Objects)를 사용하여이 문제를 해결할 수 있습니다.

이들은 필요한 속성을 입력하는 뷰 모델과 유사하며 컨트롤러에서 또는 AutoMapper와 같은 타사 솔루션을 사용하여 수동으로 매핑 할 수 있습니다.

DTO를 사용하면 다음을 수행 할 수 있습니다.

  • 데이터 직렬화 가능 (Json)
  • 순환 참조 제거
  • 필요하지 않은 속성을 그대로 두어 네트워크 트래픽을 줄입니다 (보기 모델).
  • 객체 평탄화 사용

나는 올해 학교에서 이것을 배우고 있으며 매우 유용한 도구입니다.


0

Entity 프레임 워크를 사용하는 경우 복잡한 모델을 Entity로 사용하는 DbContext에서 속성을 제거하십시오. 여러 모델을 Entity라는 뷰 모델에 매핑 할 때 동일한 문제가 발생했습니다.

public DbSet<Entity> Entities { get; set; }

DbContext에서 항목을 제거하면 내 오류가 해결되었습니다.


0

실행중인 경우 쿼리를 닫을 Linq to EntityClassTypewith new를 사용할 수 없습니다.selectonly anonymous types are allowed (new without type)

이 프로젝트의 스 니펫을 살펴보십시오

//...
var dbQuery = context.Set<Letter>()
                .Include(letter => letter.LetterStatus)
                .Select(l => new {Title =l.Title,ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated,LetterStatus = new {ID = l.LetterStatusID.Value,NameInArabic = l.LetterStatus.NameInArabic,NameInEnglish = l.LetterStatus.NameInEnglish} })
                               ^^ without type__________________________________________________________________________________________________________^^ without type

new keyword선택에 추가 폐쇄 를 추가 했는데이 complex properties오류가 발생합니다.

그래서 에 키워드 쿼리 ,,removeClassTypes from newLinq to Entity

SQL 문으로 변환되어 SqlServer에서 실행되기 때문에

그래서 나는 때를 사용할 수 있습니다 new with typesselect폐쇄?

당신이 다루고 있다면 그것을 사용할 수 있습니다 LINQ to Object (in memory collection)

//opecations in tempList , LINQ to Entities; so we can not use class types in select only anonymous types are allowed
var tempList = dbQuery.Skip(10).Take(10).ToList();// this is list of <anonymous type> so we have to convert it so list of <letter>

//opecations in list , LINQ to Object; so we can use class types in select
list = tempList.Select(l => new Letter{ Title = l.Title, ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated, LetterStatus = new LetterStatus{ ID = l.LetterStatus.ID, NameInArabic = l.LetterStatus.NameInArabic, NameInEnglish = l.LetterStatus.NameInEnglish } }).ToList();
                                ^^^^^^ with type 

ToList쿼리에서 실행 한 후에 선택에서 in memory collection 사용할 수 있도록 new ClassTypes되었습니다.


물론 익명 형식을 사용할 수는 있지만 LINQ-to-Entities에서 여전히 동일한 예외가 발생하므로 익명 구성원을 설정하기 위해 LINQ 쿼리 내에 엔터티를 만들 수 없습니다.
Suncat2000

0

많은 경우 변환이 필요하지 않습니다. 강력한 유형의 List를 원하는 이유를 생각하고 웹 서비스 나 데이터 표시 등의 데이터 만 원하는지 평가하십시오. 유형은 중요하지 않습니다. 그것을 읽는 방법을 알고 그것을 정의한 익명 유형에 정의 된 속성과 동일한 지 확인하면됩니다. 그것은 최적의 시나리오이며, 엔티티의 모든 필드가 필요하지 않은 것을 야기하며, 이것이 익명 유형이 존재하는 이유입니다.

간단한 방법은 다음과 같습니다.

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();

0

쿼리하는 테이블이므로 제품에 다시 매핑 할 수 없습니다. 익명 함수가 필요하면 ViewModel에 추가하고 각 ViewModel을 a에 추가 List<MyViewModel>하고 반환 할 수 있습니다. 약간의 위반이지만, nullable 날짜를 처리하는 것에 대한주의 사항이 포함되어 있습니다. 이것이 내가 처리 한 방법입니다.

바라건대 ProductViewModel:

public class ProductViewModel
{
    [Key]
    public string ID { get; set; }
    public string Name { get; set; }
}

데이터를 가져 오는 함수를 호출하는 종속성 주입 / 저장소 프레임 워크가 있습니다. Controller 함수 호출에서 게시물을 예로 사용하면 다음과 같습니다.

int categoryID = 1;
var prods = repository.GetProducts(categoryID);

리포지토리 클래스에서 :

public IEnumerable<ProductViewModel> GetProducts(int categoryID)
{
   List<ProductViewModel> lstPVM = new List<ProductViewModel>();

   var anonymousObjResult = from p in db.Products
                            where p.CategoryID == categoryID 
                            select new
                            {
                                CatID = p.CategoryID,
                                Name = p.Name
                            };

        // NOTE: If you have any dates that are nullable and null, you'll need to
        // take care of that:  ClosedDate = (DateTime?)p.ClosedDate ?? DateTime.Now

        // If you want a particular date, you have to define a DateTime variable,
        // assign your value to it, then replace DateTime.Now with that variable. You
        // cannot call a DateTime.Parse there, unfortunately. 
        // Using 
        //    new Date("1","1","1800"); 
        // works, though. (I add a particular date so I can edit it out later.)

        // I do this foreach below so I can return a List<ProductViewModel>. 
        // You could do: return anonymousObjResult.ToList(); here
        // but it's not as clean and is an anonymous type instead of defined
        // by a ViewModel where you can control the individual field types

        foreach (var a in anonymousObjResult)
        {                
            ProductViewModel pvm = new ProductViewModel();
            pvm.ID = a.CatID;  
            pvm.Name = a.Name;
            lstPVM.Add(rvm);
        }

        // Obviously you will just have ONE item there, but I built it 
        // like this so you could bring back the whole table, if you wanted
        // to remove your Where clause, above.

        return lstPVM;
    }

컨트롤러로 돌아가서 다음을 수행하십시오.

 List<ProductViewModel> lstProd = new List<ProductViewModel>();

 if (prods != null) 
 {
    // For setting the dates back to nulls, I'm looking for this value:
    // DateTime stdDate = DateTime.Parse("01/01/1800");

    foreach (var a in prods)
    {
        ProductViewModel o_prod = new ReportViewModel();
        o_prod.ID = a.ID;
        o_prod.Name = a.Name;
       // o_prod.ClosedDate = a.ClosedDate == stdDate ? null : a.ClosedDate;
        lstProd.Add(o_prod);
    }
}
return View(lstProd);  // use this in your View as:   @model IEnumerable<ProductViewModel>

-1

AsEnumerable () 만 추가하십시오.

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

8
절대로하지 마십시오! DB에서 모든 데이터를 가져온 다음 선택을 수행합니다.
Gh61

1
이것이 일부 회사에서 Linq의 사용이 금지 된 이유입니다.
hakan

-2

다음과 같이 컬렉션에 AsEnumerable을 추가 할 수 있습니다.

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

이것이 효과가 있지만 이것이 나쁜 대답 인 이유는 .... Asnumerable은 linq를 엔티티로 끝냅니다. Where 절 및 기타 모든 항목은 linq 외부에서 엔터티로 처리됩니다. 즉, 모든 제품이 검색된 후 linq에 의해 객체로 필터링됩니다. 이 외에도 위의 .ToList 답변과 거의 동일합니다. stackoverflow.com/questions/5311034/…
KenF

1
이것의 문제점은 순환 참조를 얻을 것이기 때문에 new product {Name = p.Name}을 선택하지 않고 select * from ... 수행한다는 것입니다. 그리고 당신은 단지 이름을 원합니다.
Sterling Diaz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.