C# LINQ到实体和多态性

C# LINQ到实体和多态性,c#,entity-framework,linq,C#,Entity Framework,Linq,假设我有一个简单的模型: public class Movie { public int ID { get; set; } public string Name { get; set; } } 和DbContext: public class MoviesContext : DbContext { ... public DbSet<Movie> Movies { get; set; } } 现在假设我想添加一个新模型,比如: public cla

假设我有一个简单的模型:

public class Movie
{
    public int ID { get; set; }

    public string Name { get; set; }
}
和DbContext:

public class MoviesContext : DbContext
{
    ...
    public DbSet<Movie> Movies { get; set; }
}
现在假设我想添加一个新模型,比如:

public class Person
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string MiddleName { get; set; }

    public string LastName { get; set; }

    public string FullName { get { return FirstName + (MiddleName?.Length > 0 ? $" {MiddleName}" : "") + $" {LastName}"; } }
}
我还想按姓名(即全名)过滤人员(DbSet人员)。我喜欢DRY,所以最好是概括MoviesContext的过滤方法。重要的是,我想在数据库级别进行过滤。所以我必须处理实体的LINQ

如果不是这样的话,任务相当简单。我可以使用一个抽象类并添加一个执行“contains substring”逻辑的虚拟方法。或者,我可以使用一个接口。不幸的是,由于LINQ for Entities,我无法使用FullName属性(虽然不方便,但可以接受),也无法编写类似这样的内容:

return Movies.Where(m => m.Name.Contains(filterString)).Select(m => m);
return dbset.Where(ent => ent.NameContains(filterString)).Select(ent => ent);
public interface IHasPredicateGetter<T> {
   [NotNull] Expression<Func<T, bool>> GetPredicateFromString([NotNull] string pValue);
}

public class Movie : IHasPredicateGetter<Movie> {
   public int ID { get; set; }
   public string Name { get; set; }

   public Expression<Func<Movie, bool>> GetPredicateFromString(string pValue) {
      return m => m.Name.Contains(pValue);
   }
}

那么,如何解决这个问题呢?我已经找到了一些解决办法(几乎让我的头都碎了),但我对它不是很满意。我将分别发布我的解决方案,但我希望有一个更优雅的解决方案。

我的解决方案看起来像这样

[1] 基类:

public abstract class NameFilterable
{
    protected static Expression<Func<T, bool>> False<T>() { return f => false; }

    public virtual Expression<Func<T, bool>> GetNameContainsPredicate<T>(string filterString)
    {
        return False<T>();
    }
}
[3] 我不能使用静态方法(静态不能被重写),所以我必须创建一个虚拟的t对象(t ent=new t();)

[4] 我仍然无法使用全名.Contains(filterString)


因此,问题仍然存在:也许我遗漏了什么,并且有一个更优雅的解决方案来解决这个问题?

您可以创建一个方法,负责搜索类型是否具有特定属性,并对该属性进行筛选,如果对象没有属性,则该属性只返回null。使用此选项,可以创建一个表达式来筛选此属性

//gets the property info of the property with the giving name
     public static PropertyInfo GetPropetyInfo<T>(string name)
        {
            var type = typeof(T);
            var property = type.GetProperty(name);
            return property;
        }
//Creates an expression thats represents the query
public static Func<T, bool> GetFilterExpression<T>( string propertyName, object propertyValue)
        {
            var prop = GetPropetyInfo<T>(propertyName);
            if(prop==null)return t=>false;
            var parameter = Expression.Parameter(typeof(T), "t");
            Expression expression = parameter;
            var left = Expression.Property(expression, prop);
            if (prop.PropertyType == typeof(string))
            {
                var toLower = typeof(string).GetMethods().FirstOrDefault(t => t.Name.Equals("ToLower"));
                var tlCall = Expression.Call(left, toLower);
                var right = Expression.Constant(propertyValue.ToString().ToLower());
                var contains = Expression.Call(tlCall, typeof(string).GetMethod("Contains"), right);
                var containsCall = Expression.IsTrue(contains);
                expression = Expression.AndAlso(Expression.NotEqual(left, Expression.Constant(null)), containsCall);                
            }
            else
            {
                if (prop.PropertyType.ToString().ToLower().Contains("nullable"))
                {
                    var getValue = prop.PropertyType.GetMethods().FirstOrDefault(t => t.Name.Equals("GetValueOrDefault"));
                    var getValueCall = Expression.Call(left, getValue);
                    var right = Expression.Constant(propertyValue);
                    expression = Expression.Equal(getValueCall, right);
                }
                else
                {                   
                    var value = Convert.ChangeType(propertyValue,prop.PropertyType);
                    var right = Expression.Constant(value);
                    expression = Expression.Equal(left, right);                  
                }
            }
            return Expression.Lambda<Func<T, bool>>(expression, new ParameterExpression[] { parameter }).Compile();
        }
//获取具有给定名称的属性的属性信息
公共静态属性信息GetPropertyInfo(字符串名称)
{
var类型=类型(T);
var property=type.GetProperty(名称);
归还财产;
}
//创建表示查询的表达式
公共静态函数GetFilterExpression(字符串propertyName,对象propertyValue)
{
var prop=getPropertyInfo(propertyName);
如果(prop==null)返回t=>false;
var参数=表达式参数(类型为(T),“T”);
表达式=参数;
var left=Expression.Property(Expression,prop);
if(prop.PropertyType==typeof(string))
{
var toLower=typeof(string).GetMethods().FirstOrDefault(t=>t.Name.Equals(“toLower”);
var tlCall=Expression.Call(左,下);
var right=Expression.Constant(propertyValue.ToString().ToLower());
var contains=Expression.Call(tlCall,typeof(string).GetMethod(“contains”),右);
var containsCall=Expression.IsTrue(contains);
expression=expression.AndAlso(expression.NotEqual(左,expression.Constant(null)),containsCal);
}
其他的
{
if(prop.PropertyType.ToString().ToLower().Contains(“null”))
{
var getValue=prop.PropertyType.GetMethods().FirstOrDefault(t=>t.Name.Equals(“GetValueOrDefault”);
var getValueCall=Expression.Call(左,getValue);
var right=表达式常数(propertyValue);
expression=expression.Equal(getValueCall,右);
}
其他的
{                   
var值=Convert.ChangeType(propertyValue,prop.PropertyType);
var right=表达式常数(值);
表达式=表达式.Equal(左、右);
}
}
返回表达式.Lambda(表达式,新参数表达式[]{parameter}).Compile();
}
您可以按如下方式使用它

 var expression = YOURCLASS.GetFilterExpression<Person>("LastName", "Jhon");
var result=dbset.Where(expression);
var expression=YOURCLASS.GetFilterExpression(“LastName”、“Jhon”);
var result=dbset.Where(表达式);

我已经做了一些事情来获得EF的多态性,但是在您需要可重用过滤器的特定情况下,我不确定这是否值得麻烦。我基本上也试着做同样的事情,但每次我最终都意识到没有意义。扪心自问:这样做到底有什么好处,它如何比
Where
子句提供的更灵活

有两个问题。其一是,通过使用共享接口(例如,
INamedObject
),很难或几乎不可能在两个单独的类之间使用过滤器。这是因为您需要一个强类型表达式。您可以创建一个返回强类型表达式的函数,但为什么不首先编写表达式呢?另一个问题是,对于每个搜索值,都需要一个新的过滤器表达式,这与我们现在的情况非常接近


如果你完善了这个,你会有什么?推断类型、指定搜索值以及获取可以使用的表达式的能力?这不就是我们已经拥有的吗?在
子句已经存在的地方,它们已经具有强大的键入功能,并且能够使用动态搜索值。虽然在多个地方说
x=>x.Name==value
可能会觉得有点多余,但实际上,指定这样一个简洁而强大的筛选语句的能力已经是一个非常了不起的地方了。

更仔细地阅读代码,而不是
NameFilterable
抽象类,你不能这样做吗:

return Movies.Where(m => m.Name.Contains(filterString)).Select(m => m);
return dbset.Where(ent => ent.NameContains(filterString)).Select(ent => ent);
public interface IHasPredicateGetter<T> {
   [NotNull] Expression<Func<T, bool>> GetPredicateFromString([NotNull] string pValue);
}

public class Movie : IHasPredicateGetter<Movie> {
   public int ID { get; set; }
   public string Name { get; set; }

   public Expression<Func<Movie, bool>> GetPredicateFromString(string pValue) {
      return m => m.Name.Contains(pValue);
   }
}
公共接口IHasPredicateGetter{
[NotNull]表达式GetPredicateFromString([NotNull]字符串pValue);
}
公共类电影:IHasPredicateGetter{
公共int ID{get;set;}
公共字符串名称{get;set;}
公共表达式GetPredicateFromString(字符串pValue){
返回m=>m.Name.Contains(pValue);
}
}
例如,这可以防止您需要强制转换。很难掌握jus