LINQ to Entities는 IEntity 인터페이스를 사용하여 EDM 기본 또는 열거 유형 캐스팅 만 지원합니다.


96

다음과 같은 일반 확장 방법이 있습니다.

public static T GetById<T>(this IQueryable<T> collection, Guid id) 
    where T : IEntity
{
    Expression<Func<T, bool>> predicate = e => e.Id == id;

    T entity;

    // Allow reporting more descriptive error messages.
    try
    {
        entity = collection.SingleOrDefault(predicate);
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException(string.Format(
            "There was an error retrieving an {0} with id {1}. {2}",
            typeof(T).Name, id, ex.Message), ex);
    }

    if (entity == null)
    {
        throw new KeyNotFoundException(string.Format(
            "{0} with id {1} was not found.",
            typeof(T).Name, id));
    }

    return entity;
}

불행히도 Entity Framework는 predicateC #이 조건자를 다음으로 변환했기 때문에 처리 방법을 모릅니다 .

e => ((IEntity)e).Id == id

Entity Framework에서 다음 예외가 발생합니다.

'IEntity'유형을 'SomeEntity'유형으로 캐스트 할 수 없습니다. LINQ to Entities는 EDM 기본 형식 또는 열거 형식 캐스팅 만 지원합니다.

Entity Framework가 IEntity인터페이스 와 함께 작동하도록하려면 어떻게 해야합니까?

답변:


188

class확장 메서드에 일반 형식 제약 조건을 추가하여이 문제를 해결할 수있었습니다 . 그래도 왜 작동하는지 모르겠습니다.

public static T GetById<T>(this IQueryable<T> collection, Guid id)
    where T : class, IEntity
{
    //...
}

6
나에게도 작동합니다! 나는 누군가가 이것을 설명 할 수 있기를 바랍니다. #linqblackmagic
BERKO

당신은 당신이 제약 추가 않았다 방법을 설명시겠습니까
yrahman

5
내 생각 엔 인터페이스 유형이 아닌 클래스 유형이 사용된다는 것입니다. EF는 인터페이스 유형에 대해 모르기 때문에 SQL로 변환 할 수 없습니다. 클래스 제약 조건에서 유추 된 유형은 EF가 수행 할 작업을 알고있는 DbSet <T> 유형입니다.
jwize

1
완벽합니다. 인터페이스 기반 쿼리를 수행하고 여전히 컬렉션을 IQueryable로 유지할 수 있다는 것이 좋습니다. 그러나 EF의 내부 작동을 알지 못하면 기본적 으로이 수정을 생각할 방법이 없다는 점이 약간 짜증납니다.
앤더스

여기에서보고있는 것은 C # 컴파일러가 T가 메서드 내에서 IEntity 유형임을 확인할 수 있도록하는 컴파일러 시간 제약으로, 컴파일 시간 동안 MSIL 코드가 생성 된 것처럼 IEntity "stuff"의 모든 사용이 유효한지 확인할 수 있습니다. 통화 전에 자동으로이 검사를 수행합니다. 명확히하기 위해 여기에 "class"를 유형 제약 조건으로 추가하면 collection.FirstOrDefault ()가 클래스 기반 유형에서 기본 ctor를 호출하는 T의 새 인스턴스를 반환 할 가능성이 있으므로 올바르게 실행될 수 있습니다.
전쟁

64

class"수정" 에 관한 몇 가지 추가 설명 .

이 대답 은 두 가지 다른 표현을 보여줍니다 where T: class. class제약 없이 우리는 :

e => e.Id == id // becomes: Convert(e).Id == id

제약 조건 :

e => e.Id == id // becomes: e.Id == id

이 두 표현식은 엔티티 프레임 워크에서 다르게 처리됩니다. EF 6 소스를 살펴보면 여기 에서 예외가 발생한다는 것을 알 수 있습니다.ValidateAndAdjustCastTypes() .

무슨 일이 벌어지는 지, EF는 IEntity 도메인 모델 세계를 이해할 수있는 것으로 하지만 그렇게하지 못하므로 예외가 발생합니다.

class제약 조건이 있는 식 에Convert() 연산자 캐스트가 시도되지 않고 모든 것이 정상입니다.

LINQ가 다른 식을 만드는 이유는 여전히 열려있는 질문으로 남아 있습니다. 일부 C # 마법사가이를 설명 할 수 있기를 바랍니다.


1
설명 해주셔서 감사합니다.
Jace Rhea

9
@JonSkeet 누군가가 여기에서 C # 마법사를 호출하려고했습니다. 어디야?
Nick N.

23

Entity Framework는 기본적으로이를 지원하지 않지만 ExpressionVisitor식을 변환하는는 쉽게 작성됩니다.

private sealed class EntityCastRemoverVisitor : ExpressionVisitor
{
    public static Expression<Func<T, bool>> Convert<T>(
        Expression<Func<T, bool>> predicate)
    {
        var visitor = new EntityCastRemoverVisitor();

        var visitedExpression = visitor.Visit(predicate);

        return (Expression<Func<T, bool>>)visitedExpression;
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert && node.Type == typeof(IEntity))
        {
            return node.Operand;
        }

        return base.VisitUnary(node);
    }
}

당신이해야 할 유일한 일은 다음과 같이 표현식 방문자를 사용하여 전달 된 조건자를 변환하는 것입니다.

public static T GetById<T>(this IQueryable<T> collection, 
    Expression<Func<T, bool>> predicate, Guid id)
    where T : IEntity
{
    T entity;

    // Add this line!
    predicate = EntityCastRemoverVisitor.Convert(predicate);

    try
    {
        entity = collection.SingleOrDefault(predicate);
    }

    ...
}

덜 유연한 또 다른 접근 방식은 다음을 사용하는 것입니다 DbSet<T>.Find.

// NOTE: This is an extension method on DbSet<T> instead of IQueryable<T>
public static T GetById<T>(this DbSet<T> collection, Guid id) 
    where T : class, IEntity
{
    T entity;

    // Allow reporting more descriptive error messages.
    try
    {
        entity = collection.Find(id);
    }

    ...
}

1

나는 같은 오류가 있지만 비슷하지만 다른 문제가 있습니다. IQueryable을 반환하는 확장 함수를 만들려고했지만 필터 기준은 기본 클래스를 기반으로했습니다.

나는 결국 내 확장 메서드가 .Select (e => e as T)를 호출하는 솔루션을 찾았습니다. 여기서 T는 자식 클래스이고 e는 기본 클래스입니다.

자세한 내용은 다음과 같습니다. EF에서 기본 클래스를 사용하여 IQueryable <T> 확장 만들기

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