RavenDB SelectMany替代方案

时间:2013-01-09 05:28:02

标签: c# ravendb linq

使用Nancy和RavenDB构建照片库 我的模型类如下:

[JsonObject(IsReference=true)]
public class Album
{
    public Album()
    {
        this.Photos = new List<Photo>();
        this.Tags = new List<String>();
    }

    public String Id {get; set;}
    public String Name {get; set;}
    public String Path {get; set;}
    public DateTime ModifiedDate{get; set;}
    public IList<String> Tags {get; set;}
    public IList<Photo> Photos {get; set;}

}

    public class Photo
{
    public String Id {get; set;}
    public String Name {get; set;}
    public String Path {get; set;}
    public IList<String> Tags {get; set;}
    public Album Album {get; set;}
    public DateTime ModifiedDate{get; set;}
    public bool IsPrivate{get; set;}
}

我在地图上的尝试减少了Photo-&gt;标签的索引:

   public class TaggedPhotosIndex:AbstractIndexCreationTask<Album, TaggedPhotos>
    {
        public TaggedPhotosIndex()
        {
            Map = albums =>
                from a in albums
                from p in a.Photos
                from t in p.Tags
                select new TaggedPhotos
                {
                    Tag = t,
                    PhotoIds = new List<String> {p.Id}
                };
            Reduce = results => 
                from result in results
                group result by result.Tag into agg
                select new TaggedPhotos
                {
                    Tag = agg.Key,
                    PhotoIds = agg.SelectMany(a => a.PhotoIds).ToList()
                };
        }
    }

    public class TaggedPhotos
    {
        public String Tag {get; set;}
        public IList<String> PhotoIds {get; set;}
    }

这是我想要实现的目标:

给定一个标签数组,我想得到所有至少有一个匹配标签的Photo对象。

RavenDB在查询中不允许使用SelectMany而且没有想法。

找到一个解决方案(使用LuceneQuery @ Workaround for selectmany in ravendb using client api),但期待任何其他替代方案。

2 个答案:

答案 0 :(得分:3)

您的文档结构存在一些问题会导致查询困难。

  • 查询设计以返回整个文档。您要求文档提供部分结果。这可以完成,但前提是您将所有字段存储在索引中并从中投影结果。您放弃了文档存储的ACID保证,这是RavenDB最强大的功能之一。

  • 您有Photo的引用回Album,这通常会被嵌入,导致循环引用,除非您设置[JsonObject(IsReference=true)]以避免这种情况。这可能适用于单个文档中的基本序列化,但在从预测索引值引用整个文档时,它毫无意义。你已经设置了自己的“鸡蛋和鸡蛋”问题。

  • 您仍然缺少人们对此问题域的基本功能。具体来说,您应该能够加载一张照片而无需加载整个相册。

强烈 建议您将照片放入自己的文档中。然后,您可以使用更简单的索引并避免循环引用。在DDD字词中,PhotoAlbum都是聚合。并且DDD聚合== RavenDB文档。您当前正在将Photo建模为不是聚合的实体,但是您要求的操作只会对聚合进行操作 - 例如搜索。

如果您只询问“给我所有相册包含标有其中一个标签的照片”,那么您就可以了。但你不是要求专辑,而是要求照片

假设你确实以这种方式建模,Brett的答案很接近 - 但是有一些无关紧要的东西。这是一个显示完整实现的单元测试。在gist上发布,因为它没有直接解决原始问题。 https://gist.github.com/4499724

答案 1 :(得分:1)

这个花了我一段时间但我确认下面的解决方案正在运行。

首先,修改索引和索引结果,如下所示。

 public class TaggedPhotosIndex:AbstractIndexCreationTask<Photo, TaggedPhotos>
    {
        public TaggedPhotosIndex()
        {
            Map = photos =>                
                from p in Photos
                from t in p.Tags
                select new TaggedPhotos
                {
                    Tag = t,
                    PhotoId = p.Id
                };
        }
    }

    public class TaggedPhotos
    {
        public string Tag {get; set;}
        public string PhotoId {get; set;}
    }

现在已经创建了索引,您可以通过以下方式查询索引以获取照片。

var tagsToSearch = new List<string>(){"test1", "test2", "test3"};

var photoIds = documentSession.Query<TaggedPhotos, TaggedPhotoIndex>()
                               .Customize(x => x.Include<Photo>(p => p.Id))
                               .Where(x => x.Tag.In(tagsToSearch))
                               .Select(x => x.PhotoId)
                               .Distinct()
                               .ToArray();

// This doesn't actually make a second call as the photos are already loaded
// in the document session
var photos = documentSession.Load<Photo>(photoIds);

根据要求

  

这是我想要实现的目标:

     

给定一个标签数组,我想获得所有具有的Photo对象   至少一个匹配的标签。

我没有看到专辑是如何真正与之相关的