diff --git a/Raven.Client.Lightweight/Indexes/AbstractIndexCreationTask.cs b/Raven.Client.Lightweight/Indexes/AbstractIndexCreationTask.cs index 6f453d592ac3..18975f966ca2 100644 --- a/Raven.Client.Lightweight/Indexes/AbstractIndexCreationTask.cs +++ b/Raven.Client.Lightweight/Indexes/AbstractIndexCreationTask.cs @@ -49,6 +49,14 @@ public abstract class AbstractIndexCreationTask public DocumentConvention Conventions { get; set; } #if !NET_3_5 + /// + /// Allows to use lambdas recursively + /// + protected IEnumerable Recurse(TSource source, Func func) + { + throw new NotSupportedException("This can only be run on the server side"); + } + /// /// Allows to use lambdas over dynamic /// diff --git a/Raven.Database/Linq/AbstractViewGenerator.cs b/Raven.Database/Linq/AbstractViewGenerator.cs index 157d9a134679..219b9881e10f 100644 --- a/Raven.Database/Linq/AbstractViewGenerator.cs +++ b/Raven.Database/Linq/AbstractViewGenerator.cs @@ -113,6 +113,27 @@ protected IEnumerable Hierarchy(object source, string name) } } + protected IEnumerable Recurse(object item, Func func) + { + if (item == null) + return Enumerable.Empty(); + + var resultsOrdered = new List(); + + var results = new HashSet(); + item = func(item); + while (item != null) + { + if (results.Add(item) == false) + break; + + resultsOrdered.Add(item); + item = func(item); + } + + return new DynamicJsonObject.DynamicList(resultsOrdered.ToArray()); + } + public void AddQueryParameterForMap(string field) { mapFields.Add(field); diff --git a/Raven.Tests/Bugs/RecursiveQueries.cs b/Raven.Tests/Bugs/RecursiveQueries.cs new file mode 100644 index 000000000000..5ccf6ea3d96b --- /dev/null +++ b/Raven.Tests/Bugs/RecursiveQueries.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using Raven.Client.Indexes; +using Xunit; +using Raven.Client.Linq; + +namespace Raven.Tests.Bugs +{ + public class RecursiveQueries : RavenTest + { + [Fact] + public void ShouldBePossible() + { + using(var store = NewDocumentStore()) + { + new CategoryWithParentsAndChildren().Execute(store); + + using(var session = store.OpenSession()) + { + var root = new Category + { + Name = "Root" + }; + session.Store(root); + var category = new Category + { + Name = "Child", + ParentId = root.Id + }; + session.Store(category); + session.Store(new Category + { + Name = "Grandchild", + ParentId = category.Id + }); + session.SaveChanges(); + } + + using(var session = store.OpenSession()) + { + List categoryHeaderWithParentses = session.Query() + .Customize(x=>x.WaitForNonStaleResults()) + .Where(x=>x.Name == "Grandchild") + .ToList(); + + Assert.Equal("Grandchild", categoryHeaderWithParentses[0].Name); + Assert.Equal("Child", categoryHeaderWithParentses[0].Parents[0].Name); + Assert.Equal("Root", categoryHeaderWithParentses[0].Parents[1].Name); + } + } + } + + public class CategoryWithParentsAndChildren : AbstractIndexCreationTask + { + public CategoryWithParentsAndChildren() + { + Map = categories => from category in categories + select new {category.Id, category.Name, category.ParentId}; + TransformResults = (database, categories) => + from category in categories + let parentCategories = Recurse(category, c => database.Load(c.ParentId)) + select new + { + category.Id, + category.Name, + Parents = + ( + from parent in parentCategories + select new {parent.Id, parent.Name} + ) + }; + } + } + + public class CategoryHeader + { + public string Id { get; set; } + public string Name { get; set; } + } + + public class CategoryHeaderWithParents : CategoryHeader + { + public CategoryHeader[] Parents { get; set; } + } + + public class Category + { + public string Id { get; set; } + public string Name { get; set; } + public string ParentId { get; set; } + } + } +} \ No newline at end of file diff --git a/Raven.Tests/Raven.Tests.csproj b/Raven.Tests/Raven.Tests.csproj index 1d5c55cc96f6..0fe4d4c831eb 100644 --- a/Raven.Tests/Raven.Tests.csproj +++ b/Raven.Tests/Raven.Tests.csproj @@ -205,6 +205,7 @@ +