将lambda表达式从客户端发送到服务器

3
我有一个方法可以帮助客户端动态构建查询:
```html

我有一个方法可以帮助客户端动态构建查询:

```
public virtual IList<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
{
    includeProperties = includeProperties ?? "";

    var qctx = new TQueryContext
    {
        QueryType = filter == null ? CommonQueryType.FillAll : CommonQueryType.FillWhere,
        Filter = filter,
        OrderBy = orderBy
    };

    qctx.Includes.AddRange(includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));

    _detachedServerRepo.Read(qctx);

    return qctx.Entities;
}

我希望将qctx发送到可能位于另一台机器上的服务器存储库。由于TQueryContext将从以下部分定义的QueryContextBase键入,因此我无法对其进行序列化。

public class QueryContextBase<TEntity, TKey>
    where TEntity : StateTrackedObject
    where TKey : IEquatable<TKey>
{
    public TKey ID { get; set; }
    public string Alf { get; set; }
    public List<TEntity> Entities { get; set; }
    public List<string> Includes { get; set; }
    public Expression<Func<TEntity, bool>> Filter { get; set; }
    public Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> OrderBy { get; set; }
}

我该如何创建类似于FilterOrderBy的属性,以便将它们序列化,然后在服务器存储库中构建查询,如下所示:

protected override void FillWhere(TQueryContext qctx)
{
    qctx.Entities.AddRange(this.Fill(qctx.Filter, qctx.OrderBy,
    qctx.GetIncludesAsString()));
}

protected override void FillAll(TQueryContext qctx)
{
    qctx.Entities.AddRange(this.Fill(null, qctx.OrderBy, qctx.GetIncludesAsString()));
}

public virtual IEnumerable<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
{
    includeProperties = includeProperties ?? "";

    try
    {
        IQueryable<TEntity> querySet = DbSet;

        if (filter != null)
        {
            querySet = querySet.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            querySet = querySet.Include(includeProperty.Trim());
        }

        return (orderBy == null) ? querySet.ToList() : orderBy(querySet).ToList();
    }
    catch (Exception ex)
    {
        return ex.ThrowDalException<IEnumerable<TEntity>>(OperationType.Read, ex.Message, ex);
    }
}

为什么不直接将DLL(库)打包并执行工作呢? - Richard Schneider
抱歉,我不明白。服务器将拥有服务器存储库代码,但客户端将发送过滤器和排序方式,例如 x=>x.ID==1234 - lacksInitiative
2个回答

2

您不想重复造轮子是正确的。请查看Serialize.Linq

您可以按照以下方式使用此库解决问题:

在您的客户端存储库中:

public virtual IList<TEntity> Fill(Expression<Func<TEntity, bool>> filter = null,
        Expression<Func<IQueryable<TEntity>, 
        IOrderedQueryable<TEntity>>> orderBy = null,
        string includeProperties = "") {

        includeProperties = includeProperties ?? "";
        try
        {
            var qctx = new TQueryContext
            {
                QueryType = filter == null ? CommonQueryType.FillAll : CommonQueryType.FillWhere,
                FilterNode = filter == null ? null : filter.ToExpressionNode(),
                OrderByNode = orderBy == null ? null : orderBy.ToExpressionNode()
            };

然后在您的 QueryContext 中添加额外属性并进行转换:

 public ExpressionNode FilterNode { get; set; }

 public Expression<Func<TEntity, bool>> Filter 
 { 
    get {
        return FilterNode == null ? null : FilterNode.ToBooleanExpression<TEntity>();
    } 
 }

 public ExpressionNode OrderByNode { get; set; }

 public Expression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>> OrderBy 
 {
    get {
        return OrderByNode ==  null ? null : OrderByNode.ToExpression<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>>();
    }
 }

0

无法序列化方法... 任何方法(lambda、delegate等),因此无法将方法从客户端发送到服务器。

你能做的最好的事情是向你的服务发送一个过滤器,构造查询并将结果返回给客户端。这是通常的方式。

所以在你的情况下,不要传递一个用于过滤数据的 Func,而是传递过滤器使用的值。

为了详细说明这一点,考虑以下服务器端方法的示例:

DataType[] GetData(Filter filter, Ordering ordering)
{
    var data = GetDataQuerySomeHow(); //for example in EF Context.Table

    //filter data according to the filter
    if (!string.IsNullOrEmpty(filter.FulltextProperty))
    {
        data = data.Where(a => a.StringProperty.Contains(filter.FulltextProperty));
    }

    //do similar thing for ordering

    return data.ToArray();
}

筛选和排序:

public class Filter
{
    public string FulltextProperty { get; set; }
    //some other filtering properties
}

public class Ordering
{
    public string ColumnName { get; set; }
    public bool Ascending { get; set; }
}

客户端

Filter filter = new Filter()
{
    //fill-in whatever you need
};

Ordering ordering = new Ordering(); //also fill in

var data = GetData(filter, ordering);
//display data somewhere

传递筛选器使用的值 - 我猜这对于泛型来说是不可能的/完全混乱 :( 谢谢。 - lacksInitiative
好的,为了明确起见,我需要为每个实体类型和每个查询版本重新编写GetData方法,对吗?因为客户可能想要过滤具有长ID或GUID的实体,或者在2、3、4个不同属性上进行过滤。那么这是否意味着每个查询类型都需要单独的GetData方法实现?也就是说,这需要大量的工作。 - lacksInitiative
@lacksInitiative:但确实可以工作:-) 顺便说一句,将每个实体标识为其他东西并不是一种很好的做法...而且为每种数据类型编写一个方法对我来说似乎非常普遍。对于每种数据类型通常甚至有多种方法-用于获取单个对象和根据某些筛选条件获取对象的方法,... - Matyas
你是指通过其他方式进行标识吗?你是在指ID的数据类型吗?一些GUID,一些长整型?我在考虑使用反射来解决这个问题。将其转换为JSON格式的字符串,如 "myproperty":"filter1", "filter2",然后从JSON字符串中反射到TEntity,并以此方式构建查询。我不可能为每个潜在方法编写一个方法。 - lacksInitiative
我选择使用 long 类型是因为它们更适合用于索引,我选择使用 GUID 类型是因为数据集的一部分来自其他系统且使用 GUID。对于您的 Filters 和 Ordering 类,我需要针对每个实体类型创建一个定制版本,是这样吗? - lacksInitiative
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接