EF.Property 抛出 "The LINQ expression could not be translated"
EF.Property throws "The LINQ expression could not be translated"
我正在尝试将软删除改造为使用 EF Core 的 ASP.NET Core 5.0 应用程序。
在DbContext
的OnModelCreating
方法中是这样做的:
builder.Entity<MyEntity>().Property<bool>("IsDeleted");
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, "IsDeleted") == false);
这个推荐在docs。我想避免更改底层数据库实体(如 MyEntity
)。
异常是在这样的代码上抛出的,它曾经完美地工作:
var myEntities= _context.MyEntities.AsNoTracking();
return _mapper.Map<IEnumerable<MyEntity>>(myEntities);
导致(在第二行):
InvalidOperationException: The LINQ expression 'DbSet<MyEntity>()
.Where(s => EF.Property<bool>(s, __ef_filter___isDeleted_0) == False)' could not be translated.
因为(自然地)有很多地方使用了 AutoMapper,所以我也不想更改用于 return 我的 DTO 的代码。调整AutoMapper配置即可。
PostgreSQL 被用作底层数据库,如果这有任何区别的话。
我需要如何配置我的查询过滤器,以便现有代码不会抛出异常?
// 编辑:
我已经简化了我的示例,确切的代码使用字符串作为 属性 名称:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, _isDeleted) == false);
我可以改变那个变量。
这里的那个东西:
HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
已完成,因为该字段是私有的。
您仍然需要实体上的字段才能完成这项工作。但是,您可以在您的自动映射器配置中忽略它,这样您就不需要调整您的 dtos。
您不能为 属性 名称使用变量。在这种情况下,EF 创建的参数在这种情况下不可翻译。
解决方案是通过辅助函数动态生成查询过滤器:
public static Expression<Func<T, bool>> GenerateDeletedFilter<T>(string propName)
{
var param = Expression.Parameter(typeof(T), "e");
var filter = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, param,
Expression.Constant(propName)), Expression.Constant(false)), param);
return filter;
}
和用法:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(GenerateDeletedFilter<MyEntity>(_isDeleted));
问题是用于指定 属性 名称的 _isDeleted
来自派生的 context[=] 的 instance 字段24=],上下文的实例成员在 global query filters 中被特殊对待。基本上它们被参数替换,在这种特殊情况下会导致“不可翻译”的构造。
解决方案是使用static
或const
代替,例如
private static readonly string _isDeleted = "IsDeleted";
或
private const string _isDeleted = "IsDeleted";
我正在尝试将软删除改造为使用 EF Core 的 ASP.NET Core 5.0 应用程序。
在DbContext
的OnModelCreating
方法中是这样做的:
builder.Entity<MyEntity>().Property<bool>("IsDeleted");
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, "IsDeleted") == false);
这个推荐在docs。我想避免更改底层数据库实体(如 MyEntity
)。
异常是在这样的代码上抛出的,它曾经完美地工作:
var myEntities= _context.MyEntities.AsNoTracking();
return _mapper.Map<IEnumerable<MyEntity>>(myEntities);
导致(在第二行):
InvalidOperationException: The LINQ expression 'DbSet<MyEntity>()
.Where(s => EF.Property<bool>(s, __ef_filter___isDeleted_0) == False)' could not be translated.
因为(自然地)有很多地方使用了 AutoMapper,所以我也不想更改用于 return 我的 DTO 的代码。调整AutoMapper配置即可。
PostgreSQL 被用作底层数据库,如果这有任何区别的话。
我需要如何配置我的查询过滤器,以便现有代码不会抛出异常?
// 编辑:
我已经简化了我的示例,确切的代码使用字符串作为 属性 名称:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<bool>(m, _isDeleted) == false);
我可以改变那个变量。
这里的那个东西:
HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
已完成,因为该字段是私有的。 您仍然需要实体上的字段才能完成这项工作。但是,您可以在您的自动映射器配置中忽略它,这样您就不需要调整您的 dtos。
您不能为 属性 名称使用变量。在这种情况下,EF 创建的参数在这种情况下不可翻译。
解决方案是通过辅助函数动态生成查询过滤器:
public static Expression<Func<T, bool>> GenerateDeletedFilter<T>(string propName)
{
var param = Expression.Parameter(typeof(T), "e");
var filter = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, param,
Expression.Constant(propName)), Expression.Constant(false)), param);
return filter;
}
和用法:
private readonly string _isDeleted = "IsDeleted";
builder.Entity<MyEntity>().Property<bool>(_isDeleted);
builder.Entity<MyEntity>().HasQueryFilter(GenerateDeletedFilter<MyEntity>(_isDeleted));
问题是用于指定 属性 名称的 _isDeleted
来自派生的 context[=] 的 instance 字段24=],上下文的实例成员在 global query filters 中被特殊对待。基本上它们被参数替换,在这种特殊情况下会导致“不可翻译”的构造。
解决方案是使用static
或const
代替,例如
private static readonly string _isDeleted = "IsDeleted";
或
private const string _isDeleted = "IsDeleted";