LINQ 泛型 IQueryable<T> WHERE 子句
LINQ Generic IQueryable<T> WHERE Clause
假设我有一个包含 3 个 DBSet 的 DBContext:
public class DatabaseContext : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
}
class A
{
public string Text { get; set; }
public string Code { get; set; }
}
class B
{
public string Name { get; set; }
public string Code { get; set; }
}
class C
{
public string Book { get; set; }
public string Code { get; set; }
}
我想编写一个通用方法:
- 一般取三个
DbSet
中的任何一个作为第一个参数
- 将字符串值作为第二个参数
- 不转换为
IEnumerable
,returns 提供的 DbSet
中的第一条记录,其中 Code
字段与提供的字符串匹配
到目前为止我有这个方法:
public static T GetCode<T>(IQueryable<T> set, string code) where T : class
{
var Prop = typeof(T).GetProperty("Code");
return set.Where(x => (string)Prop.GetValue(x) == code).FirstOrDefault();
}
当我尝试使用此行调用它时:
var _A = GetCode(TheDB.As, "123");
var _B = GetCode(TheDB.Bs, "123");
var _C = GetCode(TheDB.Cs, "123");
我收到这个错误:
InvalidOperationException:
The LINQ expression 'DbSet<A>.Where(m => (string)__Prop_0.GetValue(m) == __code_1)' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
如何在 DbSet
上编写能够为 IQueryable
正确翻译的 WHERE
子句?如果我将 IQueryable
转换为 IEnumerable
,我的方法会起作用,但我不想这样做,因为集合可能非常大,我希望数据库(而不是我的应用程序)这样做记录搜索。
最简单(且类型安全)的方法是创建一个类似 IHaveCode
的接口并将通用参数限制为它:
interface IHaveCode
{
string Code { get; set; }
}
class A: IHaveCode
{
public string Text { get; set; }
public string Code { get; set; }
}
class B: IHaveCode
{
public string Name { get; set; }
public string Code { get; set; }
}
class C: IHaveCode
{
public string Book { get; set; }
public string Code { get; set; }
}
public static T GetCode<T>(IQueryable<T> set, string code) where T : IHaveCode
{
return set.Where(x => x.Code == code).FirstOrDefault();
}
如果出于某种原因它不适合您,那么您需要自己构建一个 expression tree。
一种比 Guru Stron 的方法更通用的方法,也可用于没有 属性 Code
的 类 DbSets 的方法是提供 属性 你想在你的比较中使用。
public static T GetFirstPropertyMatchOrDefault<T>(
this IQueryable<T> source,
Expression<Func<T, string>> propertySelector,
string comparisonValue)
{
return source.Where(sourceElement => propertySelector(sourceElement) == comparisonValue)
.FirstOrDefault();
}
用法:
string comparisonValue = ...
A fetchedA = dbContext.As.GetFirstPropertyMachOrDefault(a => a.Code, comparisonValue);
但是既然你已经把它变成了通用的,你也可以用它来获取其他属性;
// Get a C by Book title:
string bookTitle = ...
C fetchedC = dbContext.Cs.GetFirstPropertyOrDefault(c => c.Book);
您可以使用 Expression Trees 实现 GetCode 方法。
public static T GetCode<T>(IQueryable<T> set, string code) where T : class {
PropertyInfo codeProp = typeof(T).GetProperty("Code");
if(codeProp == null)
throw new ArgumentException($"{typeof(T).FullName} does not have the Code property");
ParameterExpression param = Expression.Parameter(typeof(T));
Expression getCode = Expression.MakeMemberAccess(param, codeProp);
Expression codeVal = Expression.Constant(code);
Expression body = Expression.Equal(getCode, codeVal);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(body, param);
return set.FirstOrDefault(predicate);
}
假设我有一个包含 3 个 DBSet 的 DBContext:
public class DatabaseContext : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
}
class A
{
public string Text { get; set; }
public string Code { get; set; }
}
class B
{
public string Name { get; set; }
public string Code { get; set; }
}
class C
{
public string Book { get; set; }
public string Code { get; set; }
}
我想编写一个通用方法:
- 一般取三个
DbSet
中的任何一个作为第一个参数 - 将字符串值作为第二个参数
- 不转换为
IEnumerable
,returns 提供的DbSet
中的第一条记录,其中Code
字段与提供的字符串匹配
到目前为止我有这个方法:
public static T GetCode<T>(IQueryable<T> set, string code) where T : class
{
var Prop = typeof(T).GetProperty("Code");
return set.Where(x => (string)Prop.GetValue(x) == code).FirstOrDefault();
}
当我尝试使用此行调用它时:
var _A = GetCode(TheDB.As, "123");
var _B = GetCode(TheDB.Bs, "123");
var _C = GetCode(TheDB.Cs, "123");
我收到这个错误:
InvalidOperationException:
The LINQ expression 'DbSet<A>.Where(m => (string)__Prop_0.GetValue(m) == __code_1)' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
如何在 DbSet
上编写能够为 IQueryable
正确翻译的 WHERE
子句?如果我将 IQueryable
转换为 IEnumerable
,我的方法会起作用,但我不想这样做,因为集合可能非常大,我希望数据库(而不是我的应用程序)这样做记录搜索。
最简单(且类型安全)的方法是创建一个类似 IHaveCode
的接口并将通用参数限制为它:
interface IHaveCode
{
string Code { get; set; }
}
class A: IHaveCode
{
public string Text { get; set; }
public string Code { get; set; }
}
class B: IHaveCode
{
public string Name { get; set; }
public string Code { get; set; }
}
class C: IHaveCode
{
public string Book { get; set; }
public string Code { get; set; }
}
public static T GetCode<T>(IQueryable<T> set, string code) where T : IHaveCode
{
return set.Where(x => x.Code == code).FirstOrDefault();
}
如果出于某种原因它不适合您,那么您需要自己构建一个 expression tree。
一种比 Guru Stron 的方法更通用的方法,也可用于没有 属性 Code
的 类 DbSets 的方法是提供 属性 你想在你的比较中使用。
public static T GetFirstPropertyMatchOrDefault<T>(
this IQueryable<T> source,
Expression<Func<T, string>> propertySelector,
string comparisonValue)
{
return source.Where(sourceElement => propertySelector(sourceElement) == comparisonValue)
.FirstOrDefault();
}
用法:
string comparisonValue = ...
A fetchedA = dbContext.As.GetFirstPropertyMachOrDefault(a => a.Code, comparisonValue);
但是既然你已经把它变成了通用的,你也可以用它来获取其他属性;
// Get a C by Book title:
string bookTitle = ...
C fetchedC = dbContext.Cs.GetFirstPropertyOrDefault(c => c.Book);
您可以使用 Expression Trees 实现 GetCode 方法。
public static T GetCode<T>(IQueryable<T> set, string code) where T : class {
PropertyInfo codeProp = typeof(T).GetProperty("Code");
if(codeProp == null)
throw new ArgumentException($"{typeof(T).FullName} does not have the Code property");
ParameterExpression param = Expression.Parameter(typeof(T));
Expression getCode = Expression.MakeMemberAccess(param, codeProp);
Expression codeVal = Expression.Constant(code);
Expression body = Expression.Equal(getCode, codeVal);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(body, param);
return set.FirstOrDefault(predicate);
}