Reputation: 82943
Am building a Photo gallery using Nancy and RavenDB I have my model classes as below:
[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;}
}
And my attempt at map reduce index on Photo->Tags:
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;}
}
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have atleast one matching tag.
RavenDB doesn't allow SelectMany in the query and am out of ideas.
Found a solution (using LuceneQuery @ Workaround for selectmany in ravendb using client api) , but looking forward for any other alternatives.
Upvotes: 2
Views: 1876
Reputation: 3487
This one took me a while but I verified the solution below is working.
First, modify your Index and index result as shown below.
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;}
}
Now that the index is created, here is how you query against it to get your photos.
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);
Based on the requirement
Here is what I want to achieve:
Given an array of tags, I want to get all the Photo objects that have at least one matching tag.
I didn't see how album really tied into it all
Upvotes: 1
Reputation: 241900
There are some issues with your document structure that are going to make your query difficult.
Queries are designed to return whole documents. You are asking for a partial result from the document. That can be done, but only if you store all fields in the index and project the results from there. You give up the ACID guarantees of the document store, which is one of RavenDB's strongest features.
You have a reference from Photo
back to Album
which would normally be embedded, causing a circular reference, except you are setting [JsonObject(IsReference=true)]
to avoid that. This may work for basic serialization within a single document, but it is meaningless when it comes to referencing a whole document back from a projected index value. You've set up your own "chicken & egg" problem.
You still are missing basic functionality one would expect from this problem domain. Specifically, you should be able to load a single photo without having to load the whole album.
I strongly suggest that you put photos in their own documents. Then you can have much simpler indexes and avoid the circular references. In DDD terms, both Photo
and Album
are aggregates. And a DDD aggregate == a RavenDB document. You are currently modeling the Photo as an entity that is not an aggregate, but then you are asking for operations that one would only do against an aggregate - like the search.
If you were asking only "give me all albums that contain a photo tagged with one of these tags", then you would be ok. But you're not asking for the albums, you're asking for the photos.
Assuming you did model it that way, Brett's answer is close - but has some extraneous stuff. Here is a unit test showing a full implementation. Posting on gist since it does not directly address the original question. https://gist.github.com/4499724
Upvotes: 4