连续的LINQ查询

时间:2017-08-07 08:19:05

标签: c# .net linq

我正在尝试构建一个LINQ查询来查询Documents的大型SQL表(7M +条目)。

每个文档都有很多DocumentFields

Simplified UML

我的目标是在value的{​​{1}}字段上应用连续过滤器(从0到10个过滤器):

以下是我要应用的过滤器示例:

DocumentField

我想要的是检索数据库中与所有过滤器匹配的每个文档。对于前面的示例,我希望所有具有值为CET20533的文档用于具有Id“32”的字段,值为882341用于具有Id 16的字段,依此类推。

我有第一种方法:

[
  {fieldId: 32, value: "CET20533"},
  {fieldId: 16, value: "882341"},
  {fieldId: 12, value: "101746"}
]

这种方法不起作用:我的List<MyFilter> filters = ... // Json deserialization db.Documents.Where(document => filters.All(filter => document.DocumentFields.Any(documentField => documentField.Id == filter.Id && documentField.Value == filter.Value))); List不是基本类型,因此不能在LINQ查询中使用。

我有第二种方法,它没有向我抛出错误,但只应用了1个过滤器:

filters

我相信这种方法的问题是某种并发问题。我在foreach的每次迭代中应用了一个简单的暂停var result = db.Documents.Select(d => d); foreach (var filter in filters) { var id = filter.Id; var value = filter.Value; result = result.Where(document => document.DocumentFields.Any(documentField => documentField.Id == id && documentField.Value == value)); } 进行测试,它似乎有效。

问题:

  • 如何删除暂停但仍然没有并发问题?
  • 有更好的方法来构建我的查询吗?

编辑:

为了更加清晰,以下是与先前过滤器示例匹配的文档的实际示例:

Example document

3 个答案:

答案 0 :(得分:1)

你必须根据你的过滤器构建表达式并将它们分别附加在哪里(或者如果你能管理它的话)(或者不是)

db.Documents.Where(ex1).Where(ex2)...

请参阅e.gMSDN

或简单的情况:从DocumentFields开始并检索相关文档。 operation包含简单类型的工作。在构建表达式

的情况下也会更简单

答案 1 :(得分:1)

完全相信您的数据模型过于通用。在程序清晰度和性能方面会对您造成伤害。

但是让我们继续这个答案,我把它作为表达式建设的一个挑战。目标是获得一个很好的可查询,以尊重数据服务器端的过滤器。

这是我使用的数据模型,我认为它与您的数据模型非常匹配:

public sealed class Document
{
    public int Id { get; set; }
    // ...
    public ICollection<DocumentField> Fields { get; set; }
}

public sealed class DocumentField
{
    public int Id { get; set; }
    public int DocumentId { get; set; }
    public string StringValue { get; set; }
    public float? FloatValue { get; set; }
    // more typed vales here
}

首先,我实现了Conveniance函数来为各个字段类型的各个字段创建谓词:

public static class DocumentExtensions
{
    private static readonly PropertyInfo _piFieldId = (PropertyInfo)((MemberExpression)((Expression<Func<DocumentField, int>>)(f => f.Id)).Body).Member;

    private static Expression<Func<DocumentField, bool>> FieldPredicate<T>(int fieldId, T value, Expression<Func<DocumentField, T>> fieldAccessor)
    {
        var pField = fieldAccessor.Parameters[0];
        var xEqualId = Expression.Equal(Expression.Property(pField, _piFieldId), Expression.Constant(fieldId));
        var xEqualValue = Expression.Equal(fieldAccessor.Body, Expression.Constant(value, typeof(T)));
        return Expression.Lambda<Func<DocumentField, bool>>(Expression.AndAlso(xEqualId, xEqualValue), pField);
    }

    /// <summary>
    /// f => f.<see cref="DocumentField.Id"/> == <paramref name="fieldId"/> && f.<see cref="DocumentField.StringValue"/> == <paramref name="value"/>.
    /// </summary>
    public static Expression<Func<DocumentField, bool>> FieldPredicate(int fieldId, string value) => FieldPredicate(fieldId, value, f => f.StringValue);

    /// <summary>
    /// f => f.<see cref="DocumentField.Id"/> == <paramref name="fieldId"/> && f.<see cref="DocumentField.FloatValue"/> == <paramref name="value"/>.
    /// </summary>
    public static Expression<Func<DocumentField, bool>> FieldPredicate(int fieldId, float? value) => FieldPredicate(fieldId, value, f => f.FloatValue);

    // more overloads here
}

用法:

var fieldPredicates = new[] {
        DocumentExtensions.FieldPredicate(32, "CET20533"), // f => f.Id == 32 && f.StringValue == "CET20533"
        DocumentExtensions.FieldPredicate(16, "882341"),
        DocumentExtensions.FieldPredicate(12, 101746F) // f => f.Id == 12 && f.FloatValue == 101746F
};

其次,我实现了一个扩展方法HavingAllFields(也在DocumentExtensions中),它创建了一个IQueryable<Document>,其中至少有一个字段满足所有字段谓词:

    private static readonly MethodInfo _miAnyWhere = ((MethodCallExpression)((Expression<Func<IEnumerable<DocumentField>, bool>>)(fields => fields.Any(f => false))).Body).Method;
    private static readonly Expression<Func<Document, IEnumerable<DocumentField>>> _fieldsAccessor = doc => doc.Fields;

    /// <summary>
    /// <paramref name="documents"/>.Where(doc => doc.Fields.Any(<paramref name="fieldPredicates"/>[0]) && ... )
    /// </summary>
    public static IQueryable<Document> HavingAllFields(this IQueryable<Document> documents, IEnumerable<Expression<Func<DocumentField, bool>>> fieldPredicates)
    {
        using (var e = fieldPredicates.GetEnumerator())
        {
            if (!e.MoveNext()) return documents;

            Expression predicateBody = Expression.Call(_miAnyWhere, _fieldsAccessor.Body, e.Current);
            while (e.MoveNext())
                predicateBody = Expression.AndAlso(predicateBody, Expression.Call(_miAnyWhere, _fieldsAccessor.Body, e.Current));
            var predicate = Expression.Lambda<Func<Document, bool>>(predicateBody, _fieldsAccessor.Parameters);
            return documents.Where(predicate);
        }
    }

测试:

var documents = (new[]
{
    new Document
    {
        Id = 1,
        Fields = new[]
        {
            new DocumentField { Id = 32, StringValue = "CET20533" },
            new DocumentField { Id = 16, StringValue = "882341" },
            new DocumentField { Id = 12, FloatValue = 101746F },
        }
    },
    new Document
    {
        Id = 2,
        Fields = new[]
        {
            new DocumentField { Id = 32, StringValue = "Bla" },
            new DocumentField { Id = 16, StringValue = "882341" },
            new DocumentField { Id = 12, FloatValue = 101746F },
        }
    }
}).AsQueryable();
var matches = documents.HavingAllFields(fieldPredicates).ToList();

匹配文档1,但不匹配2.

答案 2 :(得分:0)

我通常会这样做:将过滤器的所有所需ID放入列表中,然后使用contains

List<int> myDesiredIds = new List<int> { 1, 2, 3, 4, 5 };
db.documents.Where(x=>myDesiredIds.Contains(x.DocumentId));