问题
Given the following object structure:
public class Object
{
public string Id {get;set;}
public List<SubObject> SubObjects {get;set;}
}
public class SubObject {get;set;}
{
public string Id {get;set;}
public string Name {get;set;}
}
How would I structure a query to return a List of SubObject where Name.Contains("a")
I feel like it should be straightforward but I'm really struggling with it.
回答1:
Short answer, you don't. Documents in RavenDb are aggregate roots and you can't load part of an aggregate root, just the whole thing. So you can get all the Objects
that contain Subobjects
with Names containing 'a'
, but you can't get Subobjects on their own. Make SubObjects their own documents if you need to get them separately.
回答2:
On your root object, you need to store the IDs of the subObjects. Then, when you load the root object, you need to use the Raven Include() functionality in your query. That will pull all of the subObject documents into the session. From there, you can then load your subObjects. Here is an example of how I've been doing it:
Here is a root object:
public class Application : EntityBase
Application has subObjects of type CustomVariableGroup. So I need to store the IDs (that's what RavenDB will save) in the object.
public List<string> CustomVariableGroupIds { get; set; } // For RavenDB
And the actual subObjects are still stored on the root object, but don't get saved in Raven that way:
[JsonIgnore] // We do not want RavenDB to serialize this.
public ObservableCollection<CustomVariableGroup> CustomVariableGroups
So that's the setup, now here's how it's dealt with in Raven. (Forget the ExecuteQuery() method; that's my own that's beyond the scope of this question.)
This is the call to get the root object. Notice the Include() where we pull the IDs into the session:
public Application GetByName(string name)
{
return ExecuteQuery<Application>(() =>
{
Application application = QuerySingleResultAndSetEtag(session =>
session.Query<Application>()
.Include(x => x.CustomVariableGroupIds)
.Where(app => app.Name == name).FirstOrDefault())
as Application;
HydrateApplication(application);
return application;
});
}
And here is how we load the subObjects:
public static void HydrateApplication(Application app)
{
if (app == null) { throw new ArgumentNullException("app"); }
if (app.CustomVariableGroupIds == null) { return; }
app.CustomVariableGroups = new ObservableCollection<CustomVariableGroup>();
foreach (string groupId in app.CustomVariableGroupIds)
{
app.CustomVariableGroups.Add(QuerySingleResultAndSetEtag(session => session.Load<CustomVariableGroup>(groupId)) as CustomVariableGroup);
}
}
Finally, when saving the root object, I make sure to save the subObject IDs:
private static void SetCustomVariableGroupIds(Application application)
{
application.CustomVariableGroupIds = new List<string>();
if (application.CustomVariableGroups == null || application.CustomVariableGroups.Count < 1) { return; }
foreach (CustomVariableGroup group in application.CustomVariableGroups)
{
application.CustomVariableGroupIds.Add(group.Id);
}
}
Hope that helps. This allows one to keep a domain model "pure," IMO. Entities can hold references to other entities, and not just denormalized versions of other entities.
Okay, and to actually answer your question... Once your document is loaded, you can simply use LINQ to query the subObjects, since now they will actually be there in memory.
回答3:
Session.Query<Object>.Where(x => x.SubObject.Any(a => a.Name.StartsWith("a"))).ToList();
I'm not sure if Contains
works, but this should get you started.
来源:https://stackoverflow.com/questions/9500043/query-list-of-sub-documents-with-ravendb