EF核心:过滤匹配列表中任何项目的记录

时间:2016-11-02 23:43:29

标签: c# entity-framework .net-core

(已编辑)我正在使用文档表和文档三元组表来试验EF核心。每个文档可以包含0个或更多(RDF)三元组,由谓词和对象表示。

相应的EF实体是:

public sealed class Document
{
    public string Id { get; set; }
    public string CreatorId { get; set; }
    public string CategoryId { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    // ...
    public IList<DocumentTriple> DocumentTriples { get; set; }
}

public sealed class DocumentTriple
{
    public int Id { get; set; }
    public string DocumentId { get; set; }
    public string Predicate { get; set; }
    public string Object { get; set; }
    public Document Document { get; set; }
}

他们的SQL定义:

CREATE TABLE [dbo].[Document](
    [Id] [varchar](32) NOT NULL,
    [CreatorId] [varchar](50) NOT NULL,
    [CategoryId] [varchar](20) NOT NULL,
    [Title] [nvarchar](500) NOT NULL,
    [Author] [nvarchar](500) NOT NULL,
    /* ... */
 CONSTRAINT [PK_dbo.Document] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[DocumentTriple](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [DocumentId] [varchar](32) NOT NULL,
    [Predicate] [varchar](100) NOT NULL,
    [Object] [nvarchar](1000) NOT NULL,
 CONSTRAINT [PK_DocumentTriple] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DocumentTriple]  WITH CHECK ADD  CONSTRAINT [FK_dbo.DocumentTriple_dbo.Document_DocumentId] FOREIGN KEY([DocumentId])
REFERENCES [dbo].[Document] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[DocumentTriple] CHECK CONSTRAINT [FK_dbo.DocumentTriple_dbo.Document_DocumentId]
GO

现在,我想使用这种模式对文档应用一些变量过滤器:我首先创建一个IQueryable documents对象,包括三元组,然后添加越来越多像documents = documents.Where(...)这样的过滤器。

其中一个过滤器是Tuple的列表,其中1 =谓词的过滤值,2 =对象的null或过滤值。我想只获得那些三元组中至少有一个匹配过滤谓词或谓词/值对的文档。

在SQL中,我可以这样做:

select * from document
inner join documenttriple on documenttriple.documentid=document.id
where (documenttriple.[predicate]='somepredicate' and documenttriple.[object]='someobject')
or (documenttriple.[predicate]='somepredicate')

我怎么能在EF代码中这样做?我试过了:

documents = from d in documents
    join dt in db.DocumentTriples on d.Id equals dt.DocumentId
    where filter.Triples.Any(t => t.Item1 == dt.Predicate &&
        (t.Item2 == null || t.Item2 == dt.Object))
    select d;

但这似乎没有过滤三元组。

修改

我尝试使用更多代码示例更好地解释自己。这是我的(缩短的)代码:

private IQueryable<Document> ApplyDocumentFilters(IQueryable<Document> documents, 
    DocumentFilter filter)
{
    if (!String.IsNullOrEmpty(filter.CreatorId))
        documents = documents.Where(d => d.CreatorId == filter.CreatorId);
    if (!String.IsNullOrEmpty(filter.CategoryId))
        documents = documents.Where(d => d.CategoryId == filter.CategoryId);
    if (!String.IsNullOrEmpty(filter.Title))
        documents = documents.Where(d => d.Title.Contains(filter.Title));
    // ...and so forth for all the filter properties...
    // here I want to filter only the documents which have at least 1 of their triples
    // matching any of the filter's triples. DOES NOT WORK
    if (filter.Triples.Count > 0)
    {
        documents = from d in documents
                    join dt in db.DocumentTriples on d.Id equals dt.DocumentId
                    where filter.Triples.Any(t => t.Item1 == dt.Predicate &&
                                                  (t.Item2 == null || t.Item2 == dt.Object))
                    select d;
    }
    return documents;
}

public PagedData<Document> GetDocuments(DocumentFilter filter)
{
    if (filter == null) throw new ArgumentNullException(nameof(filter));
    using (CatalogContext db = new CatalogContext(_options))
    {
        IQueryable<Document> documents = db.Documents
                        .Include(d => d.DocumentTriples)
                        .AsNoTracking().AsQueryable();
        documents = ApplyDocumentFilters(documents, filter);
        int total = documents.Count();
        documents = documents.OrderBy(d => d.Title).ThenBy(d => d.PublicationYear);
        return new PagedData<Document>(total,
            documents.Skip((filter.PageNumber - 1) * filter.PageSize).Take(filter.PageSize).ToList());
    }
}

如您所见,我收到一个包含我想要应用的所有过滤器的过滤器对象,然后按属性构建IQueryable属性。至于三元组,我希望得到的所有文件至少有一个三元组,它与任何过滤器的三元组相匹配。

1 个答案:

答案 0 :(得分:0)

Linq中的SQL查询是

var i = from x in Db.Documents // SELECT * FROM Document
        join y in Db.DcumentTriple // JOIN DocumentTriples 
        on x.Id equals y.DocumentId // ON DocumentId = DocumentId
        where (y.Predicate.Equals("somepredicate") && // WHERE ( DocumentTriple.[Predicate] = 'somepredicate' &&
        y.Object.Equals("someobject")) || // DocumentTriple.[Object] = 'someobject') ||
        y.Predicate.Equals("somepredict") // DocumentTriple.[Predicate] = 'somepredicate'
        select new { x, y };

但是你不必那么长时间(我只是翻译了查询,那个查询是一个错误的查询),它可以简单地完成:

var i = Db.DocumenTriple.Where(x=> (x.Predicate.Equals("something") && x.Object.Equals("something")) || x.Predicate.Equals("something");

这将为您延迟加载Db.Document导航属性。如果您想要加载它,请在结尾处拨打.Include(x=> x.Document)

<强>更新

正如你在评论中提到的那样,如果我没有弄错的话,你会发表很多where个陈述。在这种情况下,可以采用以下方法:

public IEnumerable<Expression<Func<TestEntity, Boolean>>> filters = new List<Expression<Func<TestEntity, Boolean>>>() { };
public IQueryable<TestEntity> filter(IQueryable<TestEntity> input, IEnumerable<Expression<Func<TestEntity, Boolean>>> filters, int i) {
        if (i == 0)
            return input;
        return filter(input.Where(filters.ElementAt(i)), filters, i - 1);
}

请注意,此方法中过滤器的顺序很重要。如果您不想这样,可以使用foreach循环并concat将结果导入另一个名单。