Entity Framework Core throwing exception on querying

asp.net-core c# entity-framework-core

Question

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?

1
1
4/11/2018 2:28:51 PM

Popular Answer

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;
}
1
4/11/2018 2:34:44 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow