Skip to content

Commit

Permalink
Merge pull request #987 from SeAlwin/master
Browse files Browse the repository at this point in the history
added IncludeAll functionality
  • Loading branch information
mbdavid authored May 22, 2018
2 parents 49163e1 + ca7b98e commit 0addb09
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 7 deletions.
102 changes: 96 additions & 6 deletions LiteDB.Tests/Database/DbRef_Include_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LiteDB;

namespace LiteDB.Tests.Database
{
Expand All @@ -13,7 +12,6 @@ public class Order
public ObjectId Id { get; set; }
public Customer Customer { get; set; }
public Customer CustomerNull { get; set; }

public List<Product> Products { get; set; }
public Product[] ProductArray { get; set; }
public ICollection<Product> ProductColl { get; set; }
Expand Down Expand Up @@ -108,7 +106,7 @@ public void DbRef_Include()
// .Include(x => x.Products[0].SupplierAddress)
// .FindAll()
// .FirstOrDefault();

orders.EnsureIndex(x => x.Customer.Id);

// query orders using customer $id
Expand Down Expand Up @@ -162,16 +160,108 @@ public void DbRef_Include()
.FirstOrDefault();

// must missing customer and has only 1 product

Assert.IsNull(result2.Customer);
Assert.AreEqual(1, result2.Products.Count);

// property ProductArray contains only deleted "product1", but has no include on query, so must returns deleted

Assert.AreEqual(1, result2.ProductArray.Length);


}
}

[TestMethod]
public void DbRef_IncludeAll()
{
// Setup the mapper
var mapper = new BsonMapper()
{
SerializeNullValues = true
};

// Specify the reference fields
mapper.Entity<Order>()
.DbRef(x => x.Products, "products")
.DbRef(x => x.ProductArray, "products")
.DbRef(x => x.ProductColl, "products")
.DbRef(x => x.ProductEmpty, "products")
.DbRef(x => x.ProductsNull, "products")
.DbRef(x => x.Customer, "customers")
.DbRef(x => x.CustomerNull, "customers");

mapper.Entity<Customer>()
.DbRef(x => x.MainAddress, "addresses");

mapper.Entity<Product>()
.DbRef(x => x.SupplierAddress, "addresses");

using (var db = new LiteDatabase(new MemoryStream(), mapper))
{
// setup some sample data
var address = new Address { StreetName = "3600 S Las Vegas Blvd" };
var customer = new Customer { Name = "John Doe", MainAddress = address };

var product1 = new Product { Name = "TV", Price = 800, SupplierAddress = address };
var product2 = new Product { Name = "DVD", Price = 200 };

var customers = db.GetCollection<Customer>("customers");
var addresses = db.GetCollection<Address>("addresses");
var products = db.GetCollection<Product>("products");

// insert ref documents
addresses.Insert(address);
customers.Insert(customer);
products.Insert(new Product[] { product1, product2 });

// Insert order
var orders = db.GetCollection<Order>("orders");
var order = new Order
{
Customer = customer,
CustomerNull = null,
Products = new List<Product>() { product1, product2 },
ProductArray = new Product[] { product1 },
ProductColl = new List<Product>() { product2 },
ProductEmpty = new List<Product>(),
ProductsNull = null
};

orders.Insert(order);
orders.EnsureIndex(x => x.Customer.Id);

// query orders using customer $id
// include all references to its max depth.
var r1 = orders
.IncludeAll()
.Find(x => x.Customer.Id == 1)
.FirstOrDefault();

Assert.AreEqual(order.Id, r1.Id);
Assert.AreEqual(customer.Name, r1.Customer.Name);
Assert.AreEqual(customer.MainAddress.StreetName, r1.Customer.MainAddress.StreetName);
Assert.AreEqual(product1.Price, r1.Products[0].Price);
Assert.AreEqual(product2.Name, r1.Products[1].Name);
Assert.AreEqual(product1.Name, r1.ProductArray[0].Name);
Assert.AreEqual(product2.Price, r1.ProductColl.ElementAt(0).Price);

// Just get one record, (will be the same as before)
// But only get the references for one level deep.
var result = orders
.IncludeAll(1)
.FindAll()
.FirstOrDefault();

Assert.AreEqual(customer.Name, result.Customer.Name);
Assert.AreEqual(null, result.Customer.MainAddress.StreetName);
Assert.AreEqual(null, result.Products[0].SupplierAddress.StreetName);
Assert.AreEqual(product2.Name, result.Products[1].Name);
Assert.AreEqual(product1.Name, result.ProductArray[0].Name);
Assert.AreEqual(product2.Price, result.ProductColl.ElementAt(0).Price);
Assert.AreEqual(null, result.ProductsNull);
Assert.AreEqual(0, result.ProductEmpty.Count);
}
}
}
}
57 changes: 56 additions & 1 deletion LiteDB/Database/Collections/Include.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

Expand Down Expand Up @@ -27,13 +28,67 @@ public LiteCollection<T> Include(string path)
{
if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));

return Include(new string[] { path });
}

/// <summary>
/// Run an include action in each document returned by Find(), FindById(), FindOne() and All() methods to load DbRef documents
/// Returns a new Collection with this action included
/// </summary>
/// <param name="paths">Property paths to include.</param>
public LiteCollection<T> Include(string[] paths)
{
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}

// cloning this collection and adding this include
var newcol = new LiteCollection<T>(_name, _engine, _mapper, _log);

newcol._includes.AddRange(_includes);
newcol._includes.Add(path);

// add all paths that are not null nor empty due to previous check
newcol._includes.AddRange(paths.Where(x => !String.IsNullOrEmpty(x)));

return newcol;
}

/// <summary>
/// Run an include action in each document returned by Find(), FindById(), FindOne() and All() methods to load all DbRef documents
/// Returns a new Collection with this actions included
/// </summary>
/// <param name="maxDepth">Maximum recersive depth of the properties to include, use -1 (default) to include all.</param>
public LiteCollection<T> IncludeAll(int maxDepth = -1)
{
return Include(GetRecursivePaths(typeof(T), maxDepth, 0));
}

/// <summary>
/// Recursively get all db ref paths.
/// </summary>
/// <returns>All the paths found during recursion.</returns>
private string[] GetRecursivePaths(Type pathType, int maxDepth, int currentDepth, string basePath = null)
{
currentDepth++;

var paths = new List<string>();

if (maxDepth < 0 || currentDepth <= maxDepth)
{
var fields = _mapper.GetEntityMapper(pathType).Members.Where(x => x.IsDbRef);

basePath = string.IsNullOrEmpty(basePath) ? "$" : basePath;

foreach (var field in fields)
{
var path = field.IsList ? $"{basePath}.{field.FieldName}[*]" : $"{basePath}.{field.FieldName}";
paths.Add(path);
paths.AddRange(GetRecursivePaths(field.UnderlyingType, maxDepth, currentDepth, path));
}
}

return paths.ToArray();
}
}
}

0 comments on commit 0addb09

Please sign in to comment.