Linq2DB (SQL Server):动态链接 Sql.Property<T> 调用

问题描述

我使用此 Pull Request 中的示例作为嵌套属性链的参考。不幸的是像

Sql.Property<object>(x,"SomeClass.SomeProperty") 

不起作用。

假设我有一个模型 SortDescriptor(定义如下),它定义了我们如何动态地对代码中的项目进行排序。有没有办法动态创建表达式树来生成嵌套的 Sql.Property 调用,就像下面显示的 ToPropertyExpression 方法一样?

如果给定以下过滤器,则应从 SortBy 函数输出以下 SQL:

ORDER BY Category.EntityId,StackRank,Id

CategoryCategoryProperty 上的导航属性)

        var filter1 = new PagedFilter
        {
            Take = 25,Sort = new List<SortDescriptor>
            {
                new SortDescriptor { Selector = "Category.EntityId" },new SortDescriptor { Selector = "StackRank" },new SortDescriptor { Selector = "Id" },}
        };

以下是我使用的函数和对象:

public class CategoryPropertyRepository 
{
    public async Task<long> GetPageIndexById(PagedFilter filter,Guid id)
    {
        var entity = await DbContext.Set<Entities.CategoryProperty>()
            .Select(x => new
            {
                x.Id,RowNumber = Sql.Ext.RowNumber().Over().SortBy(x,filter.Sort).ToValue()
            }).FirstOrDefaultAsyncLinqToDB(x => x.Id == id);
        var rowNumber = entity.RowNumber;

        return rowNumber / filter.Take.Value;
    }
}

public static class IOrderExtensions
{
    [Sql.Extension("ORDER BY {entity}{filter}",TokenName = "order_by_clause",ServerSideOnly = true,BuilderType = typeof(SortByBuilder))]
    public static AnalyticFunctions.IOrderedReadyToFunction<T> SortBy<T,TEntity>(
        this AnalyticFunctions.IOverMayHavePartitionAndOrder<T> over,TEntity entity,IPagedFilter filter) => throw new InvalidOperationException("SortBy is server-side only.");

    public class SortByBuilder : Sql.IExtensionCallBuilder
    {
        public void Build(Sql.ISqExtensionBuilder builder)
        {
            var entity = builder.Arguments[1];
            var filter = builder.GetValue<IPagedFilter>("filter");
            var index = 0;
            var expression = $"ORDER BY {string.Join(",",filter.Sort.Select(x => $"{{{index++}}}{(x.Descending ? " DESC" : string.Empty)}"))}";

            List<ISqlExpression> parameters = new List<ISqlExpression>();

            foreach (var sort in filter.Sort)
            {
                var sqlExpr = builder.ConvertExpressionToSql(sort.ToPropertyExpression(entity));
                parameters.Add(sqlExpr);
            }

            builder.ResultExpression = new SqlExpression(expression,Precedence.Primary,parameters.ToArray());
        }
    }

    public static Expression ToPropertyExpression(this SortDescriptor sort,object entity)
    {
        var nameParts = sort.Selector.Split('.');

        // x.SomeClass.SomeProperty should yield something like Sql.Property<object>(Sql.Property<object>(x,"SomeClass"),"SomeProperty);
        var propertyMethod = typeof(Sql).GetMethod("Property",BindingFlags.Public | BindingFlags.Static);
        propertyMethod = propertyMethod!.MakeGenericMethod(typeof(object));
        Expression exp = null;

        for (int i = nameParts.Length - 1; i >= 0; i--)
        {
            exp = Expression.Call(null,propertyMethod,Expression.Constant(exp ?? entity),Expression.Constant(nameParts[i]));
        }

        return exp;
    }
}

public class PagedFilter : IPagedFilter
{
    public virtual int? Skip { get; set; }
    public virtual int? Take { get; set; }
    public virtual IList<SortDescriptor> Sort { get; set; }
}

public class SortDescriptor
{
    public string Selector { get; set; }
    public bool Descending { get; set; }
}

每当我尝试执行与上述类似的操作时,我都会收到此错误消息,指出它正在尝试在客户端评估 Sql.Property 表达式链:

异常信息:
LinqToDB.Linq.LinqException: 'Property' 只是服务器端方法。

堆栈跟踪:
在 LinqToDB.Sql.Property[T](Object entity,String propertyName)
在 LinqToDB.Linq.QueryRunner.SetParameters(Query query,Expression expression,IDataContext parametersContext,Object[] parameters,Int32 queryNumber,SqlParameterValues parameterValues)
在 LinqToDB.Linq.QueryRunnerBase.SetCommand(Boolean clearQueryHints)
在 LinqToDB.Data.DataConnection.QueryRunner.n__0(Boolean clearQueryHints)
在 LinqToDB.Data.DataConnection.QueryRunner.ExecuteReaderAsync(CancellationToken cancellingToken)
在 LinqToDB.Linq.QueryRunner.AsyncEnumeratorImpl1.MoveNextAsync() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 源,CancellationToken 取消令牌)
在 Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source,CancellationToken cancellationToken) at LinqToDB.AsyncExtensions.ToListAsync[TSource](IQueryable1 源,CancellationToken 令牌)
在 Experlogix.Api.DesignStudio.DataAccess.CategoryPropertyRepository.GetPageIndexById(CategoryPropertyFilter filter,Guid id) 在 D:\Dev\repos\Experlogix.Api.DesignStudio\src\Experlogix.Api.DesignStudio.DataAccess\CategoryPropertyRepository.16 在 Experlogix.Core.DataAccess.Service6.GetPageIndexById(TFilter filter,TId id) at Experlogix.Api.DesignStudio.Controllers.CategoryPropertyController.GetPageIndexAsync(Guid id,CategoryPropertyFilter filter) in D:\Dev\repos\Experlogix.Api.DesignStudio\src\Experlogix.Api.DesignStudio\Controllers\CategoryPropertyController.cs:line 46 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper,ObjectMethodExecutor executor,Object controller,Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker,ValueTask1 actionResultValueTask)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker,Task lastTask,State next,Scope scope,Object state,Boolean isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next,Scope& scope,Object& state,Boolean& isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker,Boolean isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker,Boolean isCompleted)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker,Task task,IDisposable scope)
在 Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint,Task requestTask,ILogger logger)
在 Experlogix.Api.DesignStudio.Middleware.DatabaseConnectionMiddleware.Invoke(HttpContext httpContext,IUserContext userContext,IConnectionContext connectionContext,IDatabaseClient databaseClient) 在 D:\Dev\repos\Experlogix.Api.DesignStudio\src\Experlogix.Api.DesignStudio\Middleware\DatabaseConnectionMiddleware .cs:第65行
在 Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
在 Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
在 Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
在 Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext,ISwaggerProvider swaggerProvider)
在 Experlogix.Core.AspNet.Middleware.ApiExceptionHandlerMiddleware.Invoke(HttpContext context)

任何有关如何实现与此类似的事情的指导将不胜感激!

环境详情

  • linq2db 版本:linq2db.EntityFrameworkCore 5.1.0
  • 数据库服务器:SQL Server
  • 操作系统:Windows 10
  • .NET 框架:.Net 5.0

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)