Reputation: 107
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?
Upvotes: 1
Views: 2119
Reputation: 247088
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;
}
Upvotes: 1