【问题标题】:How to use LINQ Where for generic type?如何将 LINQ Where 用于泛型类型?
【发布时间】:2018-01-10 13:17:32
【问题描述】:

我有以下通用方法,我需要能够在其中执行 LINQ Where 查询:

public static List<T> GetItems<T>(Guid parentId = new Guid()) where T : new()
{
    var db = new SQLiteConnection(_dbPath);
    List<T> result;

    if (parentId != Guid.Empty)
    {
        result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
    }
    else
    {
        result = db.Table<T>().ToList();
    }

    db.Close();
    return result;
}

编译器不喜欢下面这行

result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();

错误:无法解析“ParentId”

是否可以在 LINQ 查询中以这种方式使用泛型?请注意,类型 T 的对象将始终具有 ParentId 属性。

【问题讨论】:

标签: c# linq


【解决方案1】:

您应该使用一些包含所需值的接口来具体化T 参数。此外,您应该将此接口添加到所有包含此字段或其类的基类型的类型。

public interface IHierarchy
{
    public Guid ParentId { get; }
}

public static List<T> GetItems<T>(Guid parentId = new Guid()) 
    where T : IHierarchy, new()
{
    var db = new SQLiteConnection(_dbPath);
    List<T> result;

    if (parentId != Guid.Empty)
    {
        result = db.Table<T>().Where(i => i.ParentId.Equals(parentId)).ToList();
    }
    else
    {
        result = db.Table<T>().ToList();
    }

    db.Close();
    return result;
}

如果您有 2 种类型的实体,第一种包含必需的值,而第二种没有,那么对于这种情况,您可以有两种重载。

【讨论】:

  • 不确定new() 约束在那里做什么
  • @Vadim Martynov - 谢谢,对方法签名的更改正是我想要的。我已经有一个定义 T 的接口,所以只需按照您的示例将其添加到签名中。
【解决方案2】:

您使用了泛型类型,编译器不知道您要使用哪个entity

只需使用.NET 语言的reflection 功能即可。

result = db.Table<T>().Where(i => i.GetType().GetProperty("ParentId").GetValue(src, null).Equals(parentId)).ToList();

【讨论】:

  • 哇,我不确定这个解决方案是否有效,因为作者在 SQL 上下文中使用 LINQ,并且 where 子句应该可以转换为 SQL 语言。
  • 在使用列表操作进行 LINQ 时尝试更通用时,这是一个完美的解决方案。
【解决方案3】:

问题在于,在您的代码中,您假设每个 T 都有一个 GUID 属性 ParentId,而实际上您只要求每个 T 都有一个默认构造函数。您需要要求每个 T 都有一个 ParentId。

您可以通过要求每个 T 实现某个接口来做到这一点。就像其他答案所暗示的那样,但这很麻烦,因为对于您想要使用此功能的每个类,您都需要实现此接口。

函数Enumerable.Where 似乎能够完成相同的工作,而不需要输入项的任何接口。所以让我们使用相同的方法:

作为输入,我们告诉使用哪个属性(在您的情况下为 ParentId)以及与哪个值进行比较(在您的情况下为 parentId)。

我们唯一的要求是我们必须能够比较 ParentId 和 parentId:它应该是 IEquatable

public List<T> GetItems<T, TKey>(Func<T, TKey> keySelector, Tkey value)
    TKey : IEquatable<TKey>,
{
    ...
     result = db.Table<T>()
       .Where(t => keySelector(t).Equals(value))
       .ToList();
}

用法:

    Guid value = ...
    var items = GetItems<MyClass, Guid>(item => item.ParentId, value);

此函数也适用于其他类和其他属性:

int studentId = ...
var students = GetItems<Student, int>(student => student.Id, studentId);
var otherStudents = GetItems<Student, string>(student => student.Name, "John");

两方面的说明: - 你使用new Guid() 定义一些默认的Guid。使用Guid.Empty更快 - 您不会创建 T 类型的新项目。它们已经在您的 dbContext.Table 中。因此,您不需要 new()。 - 但是,如果您的表要求 T 是一个类,那么您应该要求它。查看您的表定义:

where T : class

【讨论】:

  • 感谢您的信息。但是,当我尝试使用 Guid.Empty 作为默认参数值时,出现以下编译器错误:“parentId”的默认参数值必须是编译时常量。
  • Guid.Empty 是一个已经存在的对象。您不能将其分配给编译时常量。但是,您可以将其分配给变量,即使它们是只读的。您也可以使用它进行比较。您想将其用作默认参数值。最好创建两个函数,一个不带参数调用另一个带值 Guid.Empty() 的函数。现在我明白了为什么所有 LINQ 函数都有两个版本:一个没有 EqualityComparer,一个有 Comparer,而不是使用默认的比较器参数初始化
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多