【问题标题】:ASP.NET Web API Endpoint for querying MongoDB collections用于查询 MongoDB 集合的 ASP.NET Web API 端点
【发布时间】:2017-06-11 12:22:41
【问题描述】:

我的数据库中有两个集合:

// UnifiedPost
public class UnifiedPost
{
    public UnifiedPost() { Id = ObjectId.GenerateNewId(); }

    [BsonId]
    public ObjectId Id { get; set; }

    public int OriginalId { get; set; }
    public string Thumbnail { get; set; }
    public string Preview { get; set; }
    [AlsoNotifyFor("IsVideo")]
    public string FullSized { get; set; }
    public char Rating { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
    public string Dimensions { get { return string.Format("{0}x{1}", Width, Height); } }
    public int Score { get; set; }
    [MongoDB.Bson.Serialization.Attributes.BsonIgnore]
    public string Tags { get; set; }
    public ICollection<ObjectId> TagIds { get; set; }
    public char Site { get; set; }

    public bool IsVideo { get { return FullSized.Contains(".mp4") || FullSized.Contains(".webm"); } }

    public string UniversalId { get; set; } // UNIQUE index (Site + Original id; fe. S3001)
}

// Tag
public class Tag
{
    public Tag() { }

    public ObjectId Id { get; set; }
    public string Name { get; set; }
}

我正在尝试创建一个 WebAPI 端点以从数据库中查询帖子:

[HttpGet, Route("post")]
public UnifiedPost[] GetPosts(string tags = null, string sites = "RSGLY", string rating = null,int page = 0, int limit = 100, string sort = "id:desc")
{
    if (limit == 0 || limit > 100) limit = 100;
    var toSkip = page * limit;
    var tagArray = tags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    var siteArray = sites.ToCharArray();
    var client = new MongoClient();
    var db = client.GetDatabase("Booru");
    var postsColl = db.GetCollection<UnifiedPost>("Posts");
    var tagsColl = db.GetCollection<BooruScraper.Models.Tag>("Tags");
    var tagIds = tagsColl.Find(o => tagArray.Contains(o.Name)).ToList().ToDictionary(o => o.Name, o => o.Id);

    var query = postsColl.Find<UnifiedPost>(o => siteArray.Contains(o.Site) && tagIds.Values.All(p => o.TagIds.Contains(p))).Project<UnifiedPost>(Builders<UnifiedPost>.Projection.Expression(p => new UnifiedPost()
    {
        FullSized = p.FullSized,
        Height = p.Height,
        Id = p.Id,
        OriginalId = p.OriginalId,
        Preview = p.Preview,
        Rating = p.Rating,
        Score = p.Score,
        Site = p.Site,
        TagIds = new ObjectId[0],
        Tags = string.Join(" ", tagsColl.Find(q => p.TagIds.Contains(q.Id), new FindOptions()).Project<string>(Builders<BooruScraper.Models.Tag>.Projection.Expression(r => r.Name)).ToList(CancellationToken.None)),
        Thumbnail = p.Thumbnail,
        UniversalId = p.UniversalId,
        Width = p.Width
    })).Skip(toSkip).ToList();
    return query.ToArray();
}

坦率地说,这段代码很丑陋,甚至没有达到最低目的。这简直太可悲了,但我想不出任何方法可以用 Find Fluent API 做到这一点。

一方面,我想按用户输入的标签过滤所有帖子,如果没有输入标签,则不应用过滤器。但我不知道如何实现这一点,更不用说如何将所有其他过滤器(站点、排序、页面...)应用于find 查询。

我在这里缺少什么?如果Find 在这种情况下我应该使用什么?

谢谢!

编辑:用最简单的术语来说,我该怎么做:

  1. 从整个帖子集合中筛选出与输入标签匹配的帖子;
  2. 从该结果进一步过滤到仅用户输入的站点;
  3. 进一步过滤收藏并删除所有与输入评级不匹配的帖子;
  4. 页面、限制等

【问题讨论】:

    标签: c# asp.net .net mongodb mongodb-query


    【解决方案1】:

    天亮了。我们从哪里开始? ;)

    除非有特定原因需要两个单独的集合,否则我建议您摆脱“标签”集合。即使您需要它(例如,为了集中管理可用标签或其他东西),您仍然希望在 UnifiedPost 文档中复制标签信息。文档数据库并不是为了真正支持连接而构建的。

    这是一个工作示例,应该让您了解如何构建它(我将您的实体剥离到相关部分):

    using MongoDB.Bson;
    using MongoDB.Driver;
    using System;
    using System.Linq;
    
    namespace ConsoleApp1
    {
        public class UnifiedPost
        {
            public ObjectId Id { get; set; }
            public string[] Tags { get; set; }
            public char Site { get; set; }
            public int Score { get; set; }
        }
    
        public class Program
        {
            static void Main(string[] args)
            {
                var collection = new MongoClient().GetDatabase("test").GetCollection<UnifiedPost>("test");
    
                collection.InsertOne(new UnifiedPost() { Site = 'X', Tags = new[] { "a" } });
                collection.InsertOne(new UnifiedPost() { Site = 'Y', Tags = new[] { "a", "b" } });
                collection.InsertOne(new UnifiedPost() { Site = 'Z', Tags = new[] { "a", "b", "c" } });
    
                // 1. first filter
                var tagsWeAreInterestedIn = new[] { "a", "b" };
                var tagsFilter = Builders<UnifiedPost>.Filter.All(up => up.Tags, tagsWeAreInterestedIn);
    
                // 2. second filter
                var sitesWeAreInterestedIn = new[] { 'X', 'Y' };
                var siteFilter = Builders<UnifiedPost>.Filter.In(up => up.Site, sitesWeAreInterestedIn);
    
                // 3. third filter: rating (would follow the exact same pattern as the sites filter so we skip that part)
    
                // search for combined filter
                var query = collection.Find(tagsFilter & siteFilter);
    
                // 4. paging (e.g. sorted by the Score property, 10 results per page, skipped to page 4)
                // please note: this example will not return any data with skip=3
                // because we do not insert enough test data - use skip(0) to get a result!
                query = query.SortBy(up => up.Score).Skip(3).Limit(10);
    
                var postsWithTagB = query.ToList();
    
                Console.ReadLine();
            }
        }
    

    【讨论】:

    • 哇,这确实有道理!不过,我已经切换到 SQL Server,并且有了合法的关系,它就容易多了。不足之处?数据库服务器占用 5GB,整个数据库的关系和所有索引是整个 MongoDB 的 3 倍,文档略多。我的VPS受苦了,我现在每兆都在担心,但至少过滤要简单得多,而且似乎也更快。
    • 如果您担心空间问题,可以在访问 MongoDB 之前添加:ConventionRegistry.Register("IgnoreIfDefault", new ConventionPack { new IgnoreIfDefaultConvention(true) }, t =&gt; true); 这将使序列化程序忽略具有默认值的字段。另外,也许这有帮助:link(页面底部有一些代码)
    猜你喜欢
    • 1970-01-01
    • 2014-11-16
    • 1970-01-01
    • 2018-03-22
    • 2017-09-15
    • 1970-01-01
    • 1970-01-01
    • 2014-07-18
    • 2017-11-16
    相关资源
    最近更新 更多