c# - 构建一组可以对任何 linq 表进行操作的通用方法

标签 c# linq dynamic-linq

问题:我们广泛使用存储库模式来促进跨多个应用程序和功能子部分对我们的数据存储(使用 LINQ 的 MS SQL)的读/写操作。我们有一系列方法,它们都做彼此相似的事情。

例如,我们有 ProcessAndSortXXXXX 类方法。

private static IEnumerable<ClassErrorEntry> ProcessAndSortClassErrorLog(IQueryable<ClassErrorDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassErrorEntry(l.Id)
            {
                ClassId = l.ClassId,
                Code = l.Code,
                Message = l.Message,
                Severity = l.Severity,
                Target = l.Target
            }
        );
}

...和...

private static IEnumerable<ClassTimerLogEntry> ProcessAndSortClassTimerLog(IQueryable<ClassTimerDb> queryable, string sortOrder)
{
    var dynamic = queryable;
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    return dynamic
        .Select(l =>
            new ClassTimerLogEntry(l.Id)
            {
                ClassName = l.ClassName,
                MethodName = l.MethodName,
                StartTime = l.StartTime,
                EndTime = l.EndTime,
                ParentId = l.ParentId,
                ExecutionOrder = l.ExecutionOrder
            }
        );
}

正如您从代码中看出的那样,它们都非常相似,直到您查看签名然后到达我们正在构建 ClassErrorEntry 和 ClassTimerLogEntry 实例的返回语句。

我想构建一个实用方法,我将把它添加到所有存储库继承自的基类中。

我希望能够传入可用于实例化对象并将它们打包到返回的 IEnumerable 中的参数。

我找到了 this post通过 ScottGu这让我得到了大部分我需要的东西。它看起来像这样(来自文档中的示例):

var query =
    db.Customers.
    Where("City = @0 and Orders.Count >= @1", "London", 10).
    OrderBy("CompanyName").
    Select("new(CompanyName as Name, Phone)");

不过,这就是我卡住的地方。我需要一个指针或建议如何以通用方式传入 LINQ 表和 DataContext,以便构建动态查询。

如果我用伪代码模拟签名,我认为它看起来像这样:

protected internal IEnumerable ProcessAndSort(IQueryable source, string selectClause, string whereClause, string orderByClause);

我意识到完成的签名可能看起来与我们弄清楚的不同。

谢谢!

更新!

我现在有可以生成匿名类型但在转换为具体类型时失败的代码。

public static IEnumerable<TResult> ProcessAndSort<T, TResult>(IQueryable<T> queryable, 
    string selector, Expression<Func<T, bool>> predicate, string sortOrder)
{
    var dynamic = queryable.Where(predicate).AsQueryable();
    if (!String.IsNullOrEmpty(sortOrder.Trim()))
    {
        dynamic = dynamic.OrderBy(sortOrder);
    }
    var result= dynamic.Select(selector).Cast<TResult>();

    return result;
}

调用此方法的代码如下:

[TestMethod]
public void TestAnonymousClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0, 
        "new ( ClassId as ClassId, " +
        "Code as Code, " +
        "Message as Message, " +
        "Severity as Severity, " +
        "Target as Target )", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

最后一行 TestContext.WriteLine(result.ToList().Count.ToString()); 抛出异常 System.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1'和“Utilities.Logging.ClassErrorEntry”。

这段代码虽然失败了:

[TestMethod]
public void TestNamedClass()
{
    var loggingContext = new LoggingDbDataContext(DatabaseConnectionString);
    var repo = new LoggingRepository(loggingContext);

    var result = repo.TestGetClassErrorLog(4407, 10, 0,
        "new ClassErrorEntry(Id) { ClassId = ClassId, " +
        "Code = Code, " +
        "Message = Message, " +
        "Severity = Severity, " +
        "Target = Target }", "Target");
    TestContext.WriteLine(result.ToList().Count.ToString());
}

这会因解析错误而失败。 测试方法 eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass 抛出异常: System.Linq.Dynamic.ParseException:'('预期,在'new ClassErrorEntry(Id){ChassisAuthId = ChassisAuthId,代码=代码,消息=消息,严重性=严重性,目标中的字符19处找到'ClassErrorEntry'('标识符') =目标}'

我不确定字符位置是否可疑,因为第 19 个字符位置是 ( 并且传递给 Validate 方法的类型指示位置 4,或者第一个 'C '.

最佳答案

我完全建议您反对仅仅为了代码重用而进行弱类型查询。
代码重用是为了提高可维护性,但如果以错误的方式使用,弱类型可能会扼杀它。 通过以纯文本形式编写查询,您实际上使类很难重构和更改,并引入了许多模糊的依赖项。

我建议你看看LinqKit允许组合 Expression。例如,我们编写了一个 Paging 方法,它按页拆分查询,并在不同类型的项目中使用它:

var query = CompiledQuery.Compile(
    BuildFolderExpr( folder, false )
        .Select( msg => selector.Invoke( msg, userId ) ) // re-use selector expression
        .OrderBy( mv => mv.DateCreated, SortDirection.Descending )
        .Paging() // re-use paging expression
        .Expand() // LinqKit method that "injects" referenced expressions
    )

public static Expression<Func<T1, T2, PagingParam, IQueryable<TItem>>> Paging<T1, T2, TItem>(
    this Expression<Func<T1, T2, IQueryable<TItem>>> expr )
{
    return ( T1 v1, T2 v2, PagingParam p ) => expr.Invoke( v1, v2 ).Skip( p.From ).Take( p.Count );
}

在我的示例中,BuildMessageExpr 返回一个相对简单的选择表达式(它已经依赖于 folder 和另一个参数),不同的方法通过应用过滤、排序来重用这个表达式,获取计数,使用作为参数传递的选择器表达式进行进一步选择,等等。创建查询后,当参数相似时,它会被缓存以备将来使用。

关于c# - 构建一组可以对任何 linq 表进行操作的通用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6233402/

相关文章:

c# - NuGet 不支持 packages.config

c# - VB.NET 是否具有与自定义访问说明符等效的 C# auto 属性?

c# - 如何通过属性值选择多个 XML 标记作为 XElement?

entity-framework-4 - LINQ 动态查询库

c# - 使用 LINQ 断言数组的所有成员都是等效的?

c# - 与 oops 概念相关

c# - 使用 Entity Framework 6 调用数据库函数

c# - 用于比较具有复杂实体的两个列表的 LINQ

sql-server - 在EF6中搜索任何数据类型的文本的最有效方法是什么?

c# - 如何在动态 LINQ 中执行 "let"?