EF Core:通用 StartsWith() 表达式不可翻译
EF Core: Generic StartsWith() Expression not Translatable
我正在尝试在 EF Core 3.1.5 中构建一种通用的“StartsWith”表达式。
托管实体如下所示:
public class MyEntity
{
[System.ComponentModel.DataAnnotations.Key] // key just for the sake of having a key defined, not relevant for the question
public string TopLevelString { get; set; }
public AnotherEntity OtherEntity { get; set; }
}
public class AnotherEntity
{
[System.ComponentModel.DataAnnotations.Key] // key just for the sake of having a key defined, not relevant for the question
public string NestedString { get; set; }
}
我把它放在这样的上下文中:
public class MyDbContext : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) {}
protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite("Data Source=sqlitedemo.db");
}
并尝试在测试中使用此上下文 class:
public partial class MyTestClass // this part is just to make the example 100% reproducable
{
// define some minimal examples to work with
private List<MyEntity> testExampleList = new List<MyEntity>()
{
new MyEntity()
{
TopLevelString = "ABC",
OtherEntity = new AnotherEntity(){NestedString = "ABC"}
},
new MyEntity()
{
TopLevelString = "XYZ",
OtherEntity = new AnotherEntity(){NestedString = "XYZ"}
}
};
MyDbContext context;
public MyTestClass()
{
// set up database
var options = new DbContextOptions<MyDbContext>();
this.context = new MyDbContext(options);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// add examples from above list
this.context.MyEntities.AddRange(testExampleList);
this.context.SaveChanges();
}
}
这是我想作为普通 Where 过滤器做的事情:
public partial class MyTestClass // this part works as expected and is just used to illustrate the purpose of below code
{
[Fact]
public void TestFilteringWithoutOwnExpression()
{
Assert.Equal(1, context.MyEntities.Where(x => x.TopLevelString.StartsWith("A")).Count()); // works fine
Assert.Equal(1, context.MyEntities.Where(x => x.OtherEntity.NestedString.StartsWith("A")).Count()); // works, too
}
}
因为在应用实际的 where 子句之前应该会发生一些其他的魔法,我试图将它包装成一个自己的表达式,如下所示:
public partial class MyTestClass // this part does not work and I don' know why
{
[Fact]
public void TestFilteringWithExpression()
{
Assert.Equal(1, context.MyEntities.MyWhere<MyEntity>(x => x.TopLevelString, "A").Count());
Assert.Equal(1, context.MyEntities.MyWhere<MyEntity>(x => x.OtherEntity.NestedString, "A").Count());
}
}
with MyWhere
在扩展中定义 class:
public static class IQueryableExtension
{
public static IQueryable<TEntity> MyWhere<TEntity>(this IQueryable<TEntity> query, Expression<Func<TEntity, string>> stringSelector, string searchString)
{
ParameterExpression entityParameter = Expression.Parameter(typeof(TEntity), stringSelector.Parameters.First().Name);
MemberExpression memberExpr = (MemberExpression)(stringSelector.Body);
var searchConstant = Expression.Constant(searchString, typeof(string));
var filterExpression = Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
memberExpr,
typeof(string).GetMethod(nameof(string.StartsWith), new Type[] { typeof(string) }),
searchConstant),
entityParameter);
query = query.Where(filterExpression);
return query;
}
}
我看到了类似的示例,其中使用了 PropertyExpression 而不是 MemberExpression,但是当我不仅尝试访问 MyEntity.TopLevelString
还尝试访问嵌套的 MyEntity.AnotherEntity.NestedString
.
代码失败并出现 InvalidOperationException:
The LINQ expression 'DbSet
.Where(m => x.TopLevelString != null && "A" != null && x.TopLevelString.StartsWith("A"))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation...+
如何设置通用且可翻译的 StartsWith 表达式?
嗯,这个问题与StartsWith
无关。如果您检查异常 DbSet .Where(m => x.TopLevelString != null && "A" != null && x.TopLevelString.StartsWith("A")
以 m =>
开头但内部使用 x
这是由 ParameterExpression entityParameter = Expression.Parameter(typeof(TEntity), stringSelector.Parameters.First().Name);
行引起的。您使用该参数名称生成另一个参数,但它们不相同。
有 2 个解决方案。
- 就是直接使用参数,在上面扩展你的表达式
- 用新创建的多余参数重写您的成员表达式。
所以基本上这将解决您的问题:
ParameterExpression entityParameter = stringSelector.Parameters.First();
如果你在像 List<T>
这样的集合上尝试你的扩展方法,你会得到如下错误:
System.InvalidOperationException: 'variable 'x' of type 'ExpressionTest.MyEntity' referenced from scope '', but it is not defined'
我正在尝试在 EF Core 3.1.5 中构建一种通用的“StartsWith”表达式。 托管实体如下所示:
public class MyEntity
{
[System.ComponentModel.DataAnnotations.Key] // key just for the sake of having a key defined, not relevant for the question
public string TopLevelString { get; set; }
public AnotherEntity OtherEntity { get; set; }
}
public class AnotherEntity
{
[System.ComponentModel.DataAnnotations.Key] // key just for the sake of having a key defined, not relevant for the question
public string NestedString { get; set; }
}
我把它放在这样的上下文中:
public class MyDbContext : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) {}
protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite("Data Source=sqlitedemo.db");
}
并尝试在测试中使用此上下文 class:
public partial class MyTestClass // this part is just to make the example 100% reproducable
{
// define some minimal examples to work with
private List<MyEntity> testExampleList = new List<MyEntity>()
{
new MyEntity()
{
TopLevelString = "ABC",
OtherEntity = new AnotherEntity(){NestedString = "ABC"}
},
new MyEntity()
{
TopLevelString = "XYZ",
OtherEntity = new AnotherEntity(){NestedString = "XYZ"}
}
};
MyDbContext context;
public MyTestClass()
{
// set up database
var options = new DbContextOptions<MyDbContext>();
this.context = new MyDbContext(options);
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// add examples from above list
this.context.MyEntities.AddRange(testExampleList);
this.context.SaveChanges();
}
}
这是我想作为普通 Where 过滤器做的事情:
public partial class MyTestClass // this part works as expected and is just used to illustrate the purpose of below code
{
[Fact]
public void TestFilteringWithoutOwnExpression()
{
Assert.Equal(1, context.MyEntities.Where(x => x.TopLevelString.StartsWith("A")).Count()); // works fine
Assert.Equal(1, context.MyEntities.Where(x => x.OtherEntity.NestedString.StartsWith("A")).Count()); // works, too
}
}
因为在应用实际的 where 子句之前应该会发生一些其他的魔法,我试图将它包装成一个自己的表达式,如下所示:
public partial class MyTestClass // this part does not work and I don' know why
{
[Fact]
public void TestFilteringWithExpression()
{
Assert.Equal(1, context.MyEntities.MyWhere<MyEntity>(x => x.TopLevelString, "A").Count());
Assert.Equal(1, context.MyEntities.MyWhere<MyEntity>(x => x.OtherEntity.NestedString, "A").Count());
}
}
with MyWhere
在扩展中定义 class:
public static class IQueryableExtension
{
public static IQueryable<TEntity> MyWhere<TEntity>(this IQueryable<TEntity> query, Expression<Func<TEntity, string>> stringSelector, string searchString)
{
ParameterExpression entityParameter = Expression.Parameter(typeof(TEntity), stringSelector.Parameters.First().Name);
MemberExpression memberExpr = (MemberExpression)(stringSelector.Body);
var searchConstant = Expression.Constant(searchString, typeof(string));
var filterExpression = Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
memberExpr,
typeof(string).GetMethod(nameof(string.StartsWith), new Type[] { typeof(string) }),
searchConstant),
entityParameter);
query = query.Where(filterExpression);
return query;
}
}
我看到了类似的示例,其中使用了 PropertyExpression 而不是 MemberExpression,但是当我不仅尝试访问 MyEntity.TopLevelString
还尝试访问嵌套的 MyEntity.AnotherEntity.NestedString
.
代码失败并出现 InvalidOperationException:
The LINQ expression 'DbSet .Where(m => x.TopLevelString != null && "A" != null && x.TopLevelString.StartsWith("A"))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation...+
如何设置通用且可翻译的 StartsWith 表达式?
嗯,这个问题与StartsWith
无关。如果您检查异常 DbSet .Where(m => x.TopLevelString != null && "A" != null && x.TopLevelString.StartsWith("A")
以 m =>
开头但内部使用 x
这是由 ParameterExpression entityParameter = Expression.Parameter(typeof(TEntity), stringSelector.Parameters.First().Name);
行引起的。您使用该参数名称生成另一个参数,但它们不相同。
有 2 个解决方案。
- 就是直接使用参数,在上面扩展你的表达式
- 用新创建的多余参数重写您的成员表达式。
所以基本上这将解决您的问题:
ParameterExpression entityParameter = stringSelector.Parameters.First();
如果你在像 List<T>
这样的集合上尝试你的扩展方法,你会得到如下错误:
System.InvalidOperationException: 'variable 'x' of type 'ExpressionTest.MyEntity' referenced from scope '', but it is not defined'