实体框架核心在查询

时间:2018-04-10 19:06:43

标签: c# asp.net-core entity-framework-core

我在项目中使用通用存储库类,因此通过依赖注入实例化实际的具体存储库:

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

下面是通用存储库本身,请注意我正在使用规范模式进行查询过滤。规范本身只返回一个Expression<TEntity, bool>表达式对象。

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);
    }
}

仅返回具有确认电子邮件地址的乘客的规范的实施:

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

另一个通过电话号码匹配乘客

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;
    }
}

因此,基本上当我使用FindOne方法从存储库查询数据时,EF Core会抛出这个:

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

我是如何解决这个问题的?

1 个答案:

答案 0 :(得分:0)

这是有效的,因为表达式是将实体成员Passenger.EmailConfirmed与常量值true进行比较。

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

然而在这种情况下

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
    }
}

表达式是将实体成员Passenger.PhoneNumberstring的{​​{1}}进行比较,后者是_phoneNumber派生的ValueOject

Entity Framework无法将表达式中的值对象转换为有效的SQL。

PhoneNumber值对象没有隐式或显式转化,因此在生成查询时,很可能只会调用与任何电话号码都不匹配的PhoneNumber

我的建议是允许值对象将转换能力转换为最有可能与之相比较或最少修改规范以进行有效比较。

让我们说像

ToString