它比这有点复杂。这里的问题是 linq 查询被转换为 SQL。实体框架只能转换为包含“已知”类型的 SQL 表达式 - 即来自模型的类型。EntityKey 不是这样的类型,因此是例外。您仍然尝试做的事情是可能的,但需要一些 Linq 表达式和反射魔法。我想出了一个这样的方法:
private static IQueryable<TEntity> DynamicContains<TEntity, TProperty>(IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> property, IEnumerable<TProperty> values)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null || !(memberExpression.Member is PropertyInfo))
{
throw new ArgumentException("Property expression expected", "property");
}
// get the generic .Contains method
var containsMethod =
typeof(Enumerable)
.GetMethods()
.Single(m => m.Name == "Contains" && m.GetParameters().Length == 2);
// convert the generic .Contains method so that is matches the type of the property
containsMethod = containsMethod.MakeGenericMethod(typeof(TProperty));
// build e => Enumerable.Contains(values, e.Property)
var lambda =
Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
containsMethod, Expression.Constant(values), property.Body),
property.Parameters.Single());
// return query.Where(e => Enumerable.Contains(values, e.Property))
return query.Where(lambda);
}
它是您的实体可能拥有的任何属性(不仅是键)上的通用 Contains。它需要 IQueryable,因此可以应用于任何查询(不仅是 DbSet/ObjectSet)并返回 IQueryable,因此您可以在它之上进一步构建。不要被所有的尖括号吓倒。您可以像这样使用此方法:
var entities = DynamicContains(ctx.EntitySet, e => e.Id, new[] { 1, 4 });
e => e.Id 是一种告诉包含应该使用什么属性的奇特方式。
这是一个完整的例子,展示了这个方法:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication3
{
class MyEntity
{
public int Id { get; set; }
public string Description { get; set; }
}
class MyContext : DbContext
{
public DbSet<MyEntity> EntitySet { get; set; }
}
class Program
{
private static void Seed()
{
using (var ctx = new MyContext())
{
if (!ctx.EntitySet.Any())
{
ctx.EntitySet.Add(new MyEntity() { Description = "abc" });
ctx.EntitySet.Add(new MyEntity() { Description = "xyz" });
ctx.EntitySet.Add(new MyEntity() { Description = null });
ctx.EntitySet.Add(new MyEntity() { Description = "123" });
ctx.SaveChanges();
}
}
}
private static void PrintEntities(IEnumerable<MyEntity> entities)
{
foreach (var e in entities)
{
Console.WriteLine("Id: {0}, Description: {1}", e.Id, e.Description);
}
}
static void Main(string[] args)
{
List<int> list = new List<int>() { 1, 3 };
Seed();
using (var ctx = new MyContext())
{
PrintEntities(DynamicContains(ctx.EntitySet, e => e.Id, new[] { 1, 4 }));
PrintEntities(DynamicContains(ctx.EntitySet, e => e.Description, new[] { null, "xyz" }));
}
}
private static IQueryable<TEntity> DynamicContains<TEntity, TProperty>(IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> property, IEnumerable<TProperty> values)
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null || !(memberExpression.Member is PropertyInfo))
{
throw new ArgumentException("Property expression expected", "property");
}
// get the generic .Contains method
var containsMethod =
typeof(Enumerable)
.GetMethods()
.Single(m => m.Name == "Contains" && m.GetParameters().Length == 2);
// convert the generic .Contains method so that is matches the type of the property
containsMethod = containsMethod.MakeGenericMethod(typeof(TProperty));
// build e => Enumerable.Contains(values, e.Property)
var lambda =
Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
containsMethod, Expression.Constant(values), property.Body),
property.Parameters.Single());
// return query.Where(e => Enumerable.Contains(values, e.Property))
return query.Where(lambda);
}
}
}