使用泛型类型的 C# lambda 查询

Posted

技术标签:

【中文标题】使用泛型类型的 C# lambda 查询【英文标题】:C# lambda query using generic type 【发布时间】:2016-09-14 15:08:35 【问题描述】:

我有三个类,它们都有一个属性 Date。我想编写一个通用类来返回一个日期的所有记录。 现在的问题是:如何使用泛型类型 T 编写 lambda 表达式?

代码简单如下(我不会编译,因为“r.Date”不起作用,但这是我想要达到的效果)

Class GenericService<T>: IGenericService<T> where T:class

      ...
      readonly IGenericRepository<T> _genericRepository;
      public IEnumerable<T> GetRecordList(DateTime date)
      
             var query=_genericRepository.FindBy(r=>r.Date=date);

感谢您的帮助!

问候, 蕾欧娜

【问题讨论】:

它们是否都在一个公共接口上具有Date 属性?如果没有,可以添加吗? 如果没有通用接口你可以使用反射 可以实现一些接口并使用它? 除非您有在多个表上运行的复杂算法,否则您可能会使代码过于复杂而无益。鉴于泛型在 C# 中的工作方式,请使用通用接口或 Action 和 Func。 【参考方案1】:

编写一个具有IDate 的接口,并且您的所有实体都必须实现IDate 并像这样编写GenericService

public class GenericService<T>: IGenericService<T> where T : class, IDate

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    
         var query=_genericRepository.FindBy(r => r.Date = date);
    


public interface IDate

    DateTime Date set; get; 


public class Entity : IDate

    DateTime Date  set; get; 

【讨论】:

请问您为什么要设置赏金?你不喜欢你自己的(赞成和接受的)答案吗?你到底在期待什么? @IvanStoev 原因很简单,因为我设置了我的赏金This question has not received enough attention.,而且我还想看看 romain-aga 也回答的其他解决方案对我来说很有趣。最后一个原因是我想让开发人员看到这个解决方案并考虑使用这样的接口进行通用使用 您可能需要在 FindBy 方法调用中使用第二个等号(与 OP 不同)。要么 FindBy 接受 Func&lt;T, DateTime&gt; 并且传入的函数将用搜索的日期覆盖所有日期类的 Date 属性!【参考方案2】:

另一种选择是动态构建Expression。如果您不想或不能将 IDate 接口添加到模型对象,并且如果 FindBy 需要 Expression&lt;Func&lt;T, bool&gt;&gt;(例如,它在 EntityFramework 内部转换为 IQueryable&lt;T&gt;.Where(...) 或类似。

就像@JonB 的回答一样,这没有编译时安全性来确保T 具有Date 属性或字段。它只会在运行时失败。

public IEnumerable<T> GetRecordList(DateTime date)

    var parameterExpression = Expression.Parameter(typeof(T), "object");
    var propertyOrFieldExpression = Expression.PropertyOrField(parameterExpression, "Date");
    var equalityExpression = Expression.Equal(propertyOrFieldExpression, Expression.Constant(date, typeof(DateTime)));
    var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalityExpression, parameterExpression);

    var query = _genericRepository.FindBy(lambdaExpression);
    return query;

【讨论】:

这是一个更好的解决方案,因为如果添加更多实体,实现 IDate 之后需要维护。 在我的场景中,lambda 查询将被转换为 DB 查询,并且在编码阶段不知道需要 T 的哪些属性。这个解决方案对我有用。太棒了!【参考方案3】:

如果您无法实现接口并且不想使用反射,另一种解决方案可能是将如何获取日期传递给 GenericService:

按属性:

public class GenericService<T>: IGenericService<T> where T : class

    public Func<T, DateTime> DateGetter  get; set; 

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    
        var query=_genericRepository.FindBy(r => DateGetter(r) == date);
    

由构造函数:

public class GenericService<T>: IGenericService<T> where T : class

    Func<T, DateTime> _dateGetter;

    public GenericService(..., Func<T, DateTime> dateGetter)
    
        _dateGetter = dateGetter
        ...
    

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    
        var query=_genericRepository.FindBy(r => _dateGetter(r) == date);
    

按方法本身:

public class GenericService<T>: IGenericService<T> where T : class

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date, Func<T, DateTime> dateGetter)
    
        var query=_genericRepository.FindBy(r => dateGetter(r) == date);
    

按类型:

Adam Ralph 的解决方案启发了我这个解决方案(我猜不是最好的,但它仍然有效)

public class GenericService<T>: IGenericService<T> where T : class

    public Dictionary<Type, Func<object, DateTime>> _dateGetter = new Dictionary<Type, Func<object, DateTime>>()
    
         typeof(TypeWithDate), x => ((TypeWithDate)x).Date ,
         typeof(TypeWithDate2), x => ((TypeWithDate2)x).Date 
    ;

    readonly IGenericRepository<T> _genericRepository;
    public IEnumerable<T> GetRecordList(DateTime date)
    
        var query=_genericRepository.FindBy(r => _dateGetter[typeof(T)](r) = date);
    

【讨论】:

感谢您的解决方案。其实我可以在界面中使用,但实际上,为了维护,我最好不使用它。我注意到在您的解决方案中,您还实施了 IDate ......我相信这是一个错误,不是吗?如果是,最好纠正它...再次感谢:) 是的,这是一个错误,我复制粘贴了 Arvin 答案中的代码 ^^" 感谢@romain-aga,您的解决方案也是解决此问题的好方法,但在这种情况下,如果我们使用 EntityFramework,我们需要 LINQ to Entitie,我相信运行此代码会发生此错误: The LINQ expression node type 'Invoke' is not supported in LINQ to Entities。但如果我们不使用 EntityFramework,这也是一个有效的解决方案 我将不得不做一个测试来检查你在说什么。但我认为如果 LINQ 是一个问题,您仍然可以通过更传统的方式更改所有 LINQ 查询(即使没有它处理起来会更重)。 我们是不是错过了另一个=.FindBy(r =&gt; dateGetter(r) = date);【参考方案4】:

假设对象列表不是很大,为 3 个类编写通用的东西可能会过度工程化。 KISS.

objects.Where(obj =>
    (obj as Foo)?.DateTime == date ||
    (obj as Bar)?.DateTime == date ||
    (obj as Baz)?.DateTime == date)

【讨论】:

【参考方案5】:

我不确定您的 FindBy 方法的结构或返回的内容,但您可以像这样使用 dynamic 关键字:

   var query=_genericRepository.FindBy(r=>((dynamic)r).Date==date);

如果FindBy 方法返回IEnumerable&lt;T&gt;,那么您可能需要在return 语句中添加.Cast&lt;T&gt;(),否则您可能会得到IEnumerable&lt;dynamic&gt; 的类型。

这绝不是类型安全的,如果T 没有Date 属性,您可能会遇到运行时错误。但这是需要考虑的一种选择。

【讨论】:

您好@JonB,感谢您的回答。 1/ FindBy 方法返回一个 IQueryable。 2/ 您的代码出现编译错误:表达式树可能不包含动态操作。

以上是关于使用泛型类型的 C# lambda 查询的主要内容,如果未能解决你的问题,请参考以下文章

泛型类的基本使用

详解C#泛型

C#关于反射创建泛型类

为啥 C# (4.0) 不允许泛型类类型中的协变和逆变?

c#基础泛型

C# 泛型的使用