简体   繁体   中英

Entity Framework Core throwing exception on querying

I'm using a generic repository class in my project, so an actual concrete repository is instantiated through dependency injection:

services.AddTransient<IRepository<Passenger>, EntityFrameworkRepository<Passenger>>();

Below is the generic repository itself, notice that i'm using specification pattern for query filtering. Specification themselves just return an Expression<TEntity, bool> expression object.

public class EntityFrameworkRepository<TEntity> : IRepository<TEntity>
    where TEntity : class
{
    public async Task<IEnumerable<TEntity>> Find(Specification<TEntity> specification)
    {
        return await _context.Set<TEntity>()
            .Where(specification.ToExpression())
            .AsNoTracking()
            .ToListAsync();;
    }

    public async Task<TEntity> FindOne(Specification<TEntity> specification)
    {
        return await _context.Set<TEntity>()
            .AsNoTracking()
            .FirstOrDefaultAsync(specification.ToExpression());
    }

    public async Task<TEntity> GetById(object id)
    {
        return await _context.Set<TEntity>().FindAsync(id);
    }
}

An implementation of a specification that only return passengers with confirmed email address:

public class PermanentPassengerSpecification : Specification<Passenger>
{
    public override Expression<Func<Passenger, bool>> ToExpression()
    {
        return passenger => passenger.EmailConfirmed == true;
    }
}

And another that matches passengers by phone number

public class PassengerByPhoneSpecification : Specification<Passenger>
{
    private readonly PhoneNumber _phoneNumber;

    public PassengerByPhoneSpecification(PhoneNumber phoneNumber)
    {
        if (phoneNumber == null)
            throw new ArgumentNullException();
        _phoneNumber = phoneNumber;
    }

    public override Expression<Func<Passenger, bool>> ToExpression()
    {
        return passenger => passenger.PhoneNumber == _phoneNumber;
    }
}

So, basically when i query data from the repository with FindOne method, EF Core throws this:

InvalidOperationException: The EF.Property<T> method may only be used within LINQ queries.
Microsoft.EntityFrameworkCore.EF.Property<TProperty>(object entity, string propertyName)
lambda_method(Closure , TransparentIdentifier<Passenger, PhoneNumber> )
System.Linq.AsyncEnumerable+WhereSelectEnumerableAsyncIterator+<MoveNextCore>d__8.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable+ConfiguredTaskAwaiter.GetResult()
System.Linq.AsyncEnumerable+AsyncIterator+<MoveNext>d__10.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable+ConfiguredTaskAwaiter.GetResult()
System.Linq.AsyncEnumerable+<FirstOrDefault_>d__165.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider+TaskResultAsyncEnumerable+Enumerator+<MoveNext>d__3.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider+ExceptionInterceptor+EnumeratorExceptionInterceptor+<MoveNext>d__5.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler+<ExecuteSingletonAsyncQuery>d__23.MoveNext()
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Taksapp.Infrastructure.Repositories.EntityFrameworkRepository+<FindOne>d__3.MoveNext() in EntityFrameworkRepository.cs

Any hint of how I can solve this issue?

This works because the expression is comparing the entity member Passenger.EmailConfirmed to a constant value true .

public class PermanentPassengerSpecification : Specification<Passenger> {
    public override Expression<Func<Passenger, bool>> ToExpression() {
        return passenger => passenger.EmailConfirmed == true;
    }
}

However in this case

public class PassengerByPhoneSpecification : Specification<Passenger> {
    private readonly PhoneNumber _phoneNumber;

    public PassengerByPhoneSpecification(PhoneNumber phoneNumber) {
        if (phoneNumber == null)
            throw new ArgumentNullException();
        _phoneNumber = phoneNumber;
    }

    public override Expression<Func<Passenger, bool>> ToExpression() {
        return passenger => passenger.PhoneNumber == _phoneNumber; //<--THIS WONT WORK
    }
}

the expression is comparing the entity member Passenger.PhoneNumber , which I assume is a string to the local variable _phoneNumber which is a ValueOject derived PhoneNumber .

There is no way that Entity Framework can convert that value object in the expression to valid SQL.

The PhoneNumber value object has no implicit or explicit conversions so when generating the query it would most likely just call ToString which doesn't match any phone number.

My suggestion would be allow the value object to ability the convert to what it would be most likely compared to or at the very least modify the specification to make a valid comparison.

Lets say something like

public override Expression<Func<Passenger, bool>> ToExpression() {
    string formattedNumber = 
        string.Format("{0}{1}",_phoneNumber.RegionCode, _phoneNumber.Number);
    return passenger => passenger.PhoneNumber == formattedNumber;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM