我正在尝试构建一个LINQ查询来查询Documents
的大型SQL表(7M +条目)。
每个文档都有很多DocumentFields
:
我的目标是在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));
}
进行测试,它似乎有效。
问题:
编辑:
为了更加清晰,以下是与先前过滤器示例匹配的文档的实际示例:
答案 0 :(得分:1)
你必须根据你的过滤器构建表达式并将它们分别附加在哪里(或者如果你能管理它的话)(或者不是)
db.Documents.Where(ex1).Where(ex2)...
或简单的情况:从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));