在 属性 上按 _t 过滤集合

Filtering collection by _t on property

我已经用 mongo/c# 实现了一个场景,我在同一个集合中存储不同类型的 类。一切都很好,我最终可以做到这一点:

var collection = _mongoDb.GetCollection<TLogType>(CollectionNames.Logs).OfType<TLogType>();

现在我需要类似的功能,但用于属性。基本上,我有一个包含一组联系人的集合(普通集合,不是多态集合)。我希望数组中的每个项目都是特定的联系人类型。我想出的文档看起来像这样(为简洁起见较小):

Contacts" : [ 
    {
        "_t" : [ 
            "Contact", 
            "ExternalContact"
        ],
        "Role" : "Product Emergency",
        "Name" : "Fred",
        "Email" : "fred@gmail",
        "Phone" : "1231",
        "Availability" : "",
    }, 
    {
        "_t" : [ 
            "Contact", 
            "InternalContact"
        ],
        "Role" : "Manager",
        "Name" : "Mickey"
    }
]

我现在想要的是按类型检索联系人。我们有 OfType<TResult> 但它仅适用于 MongoQueryable。所以,像这样:

public async Task<IEnumerable<string>> GetContacts<TContactType>(CancellationToken token) where TContactType : Contact
{

    var collection = _mongoDb.GetCollection<Person>(CollectionNames.Person)();

    var projectedListOfContacts =
        await collection.Find(
            s => s.Contacts != null && 
            s.Contacts.OfType<TContactType> // how to filter by type on a property?
        )
            .Project(dto => dto.Contacts).
            .ToListAsync(token);

    ...
} 

有什么方法可以做到这一点而不必获取所有联系人并在内存中使用它们?

相关的类是:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Contact> Contacts {get; set;}
}

public abstract class Contact : IContact
{
    public string Role { get; private set; }
    public string Name { get; private set; }

    protected Contact(string role, string name)
    {
        Role = role ?? throw new ArgumentNullException(nameof(role));
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}

public class ExternalContact : Contact
{

    public string Email { get; private set; }   
    public string Phone { get; private set; }   
    public string Availability { get; private set; }

    public ExternalContact(string role, string name, string email, string phone, string availability)
        : base(role, name)
    {
        Email = email ?? throw new ArgumentNullException(nameof(email));
        Phone = phone ?? throw new ArgumentNullException(nameof(phone));
        Availability = availability;
    }
}

更新: 答案帮助我弄清楚了..所以最后我可以这样做:

var contacts =
await Collection.Find(s => s.Contacts != null)
    .Project(dto => dto.Contacts.OfType<TContactType>())
    .ToListAsync(token);

事实证明,要求发生了变化.. 联系人数组将始终至少包含一种类型。所以没有理由过滤..但我利用了只返回我想要的类型的投影。

只是在这里随意拍摄,但我相信这可能就是您要找的东西,但如果没有 mcve 很难确定。

        var nullFilter = Builders<Person<TContactType>>.Filter.Ne(person => person.Contacts, null);
        var typeFilter = Builders<Person<TContactType>>.Filter.OfType<Contact, TContactType>(person => person.Contacts);
        var combinedFilter = Builders<Person<TContactType>>.Filter.And(nullFilter, typeFilter);

        var projectedListOfContact = collection.Find(combinedFilter).Project(dto => dto.Contacts).ToListAsync(token);      

更新生成器

var query = Builders<Person>.Filter.ElemMatch(x => x.Contacts, Builders<Contact>.Filter.OfType<ExternalContact>());
collection.Find(query);

这当然也可以与您的通用 T 一起使用。

这应该returnfind({ "Contacts" : { "$elemMatch" : { "_t" : "ExternalContact" } } })