构建一组可以在任何 linq 表上操作的通用方法

发布于 2024-11-14 04:53:29 字数 4640 浏览 3 评论 0原文

问题:我们广泛使用存储库模式来促进跨多个应用程序和功能子部分对数据存储(使用 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 实例的 return 语句。

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

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

我发现 这篇文章,作者: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在类型“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,Code = Code,Message = Message,Severity = Severity,Target中的字符19处找到'ClassErrorEntry'('Identifier') = Target }'

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

Problem: We make extensive use of a repository pattern to facilitate read/write operations on our datastore (MS SQL using LINQ) across multiple applications and subsections of functionality. We have series of methods that all do something similar to each other.

For example, we have the ProcessAndSortXXXXX class of methods.

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
            }
        );
}

...and...

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
            }
        );
}

As you can tell by the code, they're all very similar until you look at the signature and then get to the the return statement where we're building out the instances of the ClassErrorEntry and ClassTimerLogEntry.

I want to build a utility method that I'll add into the base class that all of the repositories inherit from.

I want to be able to pass in arguments that can be used to instantiate the objects and pack them into the returning IEnumerable.

I found this post by ScottGu and that gets me most of what I need. It looks like this (from the sample in the documentation):

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

Here's where I get stuck, though. I need a pointer or suggestion how I can pass in the LINQ tables and DataContext in a generic fashion so I can build out the dynamic query.

If I were to mock up the signature in pseudocode I think it would look something like this:

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

I realize that the finished signature may look different as we figure this out.

Thank you!

Update!

I now have code that works to generate an anonymous type but fails when converting to the concrete type.

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;
}

Here is the code that calls this method:

[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());
}

The last line TestContext.WriteLine(result.ToList().Count.ToString()); throws the exception System.InvalidOperationException: No coercion operator is defined between types 'DynamicClass1' and 'Utilities.Logging.ClassErrorEntry'.

This chunk of code, though fails:

[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());
}

This fails on a parsing error. Test method eModal.Repositories.Test.RepositoryBaseTest.TestConcreteClass threw exception:
System.Linq.Dynamic.ParseException: '(' expected, found 'ClassErrorEntry' ('Identifier') at char 19 in 'new ClassErrorEntry(Id) { ChassisAuthId = ChassisAuthId, Code = Code, Message = Message, Severity = Severity, Target = Target }'

I'm not sure that the character position is suspectas the 19th character position is a ( and the type passed into the Validate method indicates a position of 4, or the first 'C'.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

自找没趣 2024-11-21 04:53:29

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

我建议你看看 LinqKit ,它允许组合 Expressions。例如,我们编写了一个 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 和另一个参数),并且不同的方法通过应用过滤、排序、获取计数、进一步选择作为参数传递的选择器表达式等来重用此表达式。创建查询后,它将被缓存以供将来参数相似时使用。

I would completely advise you against making weakly typed queries just for the sake of code reuse.
Code reuse is for increasing maintainability, but weak typing can kill it, if used in a wrong way.
By writing your queries in plain text, you're effectively making the classes very hard to refactor and change, and introduce a lot of obscure dependencies.

I suggest you take a look at LinqKit that allows to combine Expressions. For example, we wrote a Paging method that splits query by pages and use it across the project with different types:

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 );
}

In my example, BuildMessageExpr returns a relatively simple select expression (which already depends on folder and another parameter), and different methods reuse this expression by applying filtering, ordering, getting count, further selecting with selector expression being passed as a parameter, et cetera. Once the query is created, it gets cached for future usage when parameters are similar.

贱贱哒 2024-11-21 04:53:29

这不是对你的问题的直接回答。

正如您所说,您有很多看起来相似但返回不同类型的代码。如果您继续寻找此方法的通用实现,结果可能会出现一些问题,您可能仍然会传递一些不舒服的 SQL 或检查对象的类型或执行一些反射功夫。你仍然可以选择这个通行证,实际上有人可以有一个明智的想法,不会看起来像一个肮脏的黑客。

另一种选择是使用具有通用存储库模式和依赖项注入的适当 ORM(

It is not a direct answer to your question.

As you have said you have quite a lot code that look similar but return different types. If you will go ahead and look for generic implementation of this approach, the result may have a few hacks, you may still pass some uncomfortable SQL or check the type of the object or do some reflection kung-fu. You may still select this pass and actually someone can have a sensible idea that wouldn't look like a dirty hack.

The other option is to use a proper ORM with generic repository pattern and dependency injection(google link). Your data access layer will look much simpler and easier to maintain.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文