Last active
July 9, 2020 19:33
-
-
Save Skamiplan/45a709939dcb04795a77b3937f5f797c to your computer and use it in GitHub Desktop.
A basic example on how to generically do (Entity Framework like) relations in Mongo, although at this point only 1 object deep.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using MongoDB.Bson; | |
using MongoDB.Bson.Serialization.Attributes; | |
using MongoDB.Driver; | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel.DataAnnotations.Schema; | |
using System.Diagnostics; | |
using System.Linq; | |
namespace Looking | |
{ | |
internal class Program | |
{ | |
private static void Main(string[] args) | |
{ | |
new Entry().Start(); | |
} | |
} | |
public class Entry | |
{ | |
private readonly IMongoCollection<Person> personCollection; | |
private readonly IMongoCollection<Address> addressCollection; | |
private readonly IMongoCollection<Pet> petCollection; | |
private readonly IMongoCollection<RealEstate> realEstateCollection; | |
public Entry() | |
{ | |
var client = new MongoClient("mongodb://127.0.0.1:27017"); | |
var database = client.GetDatabase("LookupTest"); | |
this.personCollection = database.GetCollection<Person>("Person"); | |
this.addressCollection = database.GetCollection<Address>("Address"); | |
this.petCollection = database.GetCollection<Pet>("Pet"); | |
this.realEstateCollection = database.GetCollection<RealEstate>("RealEstate"); | |
} | |
public class Person | |
{ | |
[BsonId] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string Id { get; set; } | |
public string FirstName { get; set; } | |
public DateTime DoB { get; set; } | |
[ForeignKey("RealEstate")] | |
public List<BsonObjectId> RealEstateIds { get; set; } | |
public List<RealEstate> RealEstate { get; private set; } | |
[ForeignKey("Address")] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string AddressId { get; set; } | |
public Address Address { get; private set; } | |
[ForeignKey("Pet")] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string PetId { get; set; } | |
public Pet Pet { get; private set; } | |
} | |
public class Pet | |
{ | |
[BsonId] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string Id { get; set; } | |
public string Name { get; set; } | |
public DateTime DoB { get; set; } | |
[ForeignKey("Address")] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string AddressId { get; set; } | |
public Address Address { get; private set; } | |
} | |
public class RealEstate | |
{ | |
[BsonId] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string Id { get; set; } | |
public decimal Value { get; set; } | |
public int SizeInSquareMeters { get; set; } | |
} | |
public class Address | |
{ | |
[BsonId] | |
[BsonRepresentation(BsonType.ObjectId)] | |
public string Id { get; set; } | |
public string Street { get; set; } | |
public string HouseNumber { get; set; } | |
public string City { get; set; } | |
} | |
private readonly List<Type> alreadyVisitedTypes = new List<Type>(); | |
public IAggregateFluent<T> BuildAggregate<T>(IAggregateFluent<T> aggregate, Type currentType, string prefix) | |
{ | |
if (this.alreadyVisitedTypes.Contains(currentType)) | |
{ | |
return aggregate; | |
} | |
this.alreadyVisitedTypes.Add(currentType); | |
foreach (var prop in currentType.GetProperties()) | |
{ | |
var foreignKeyAttributes = (ForeignKeyAttribute[])prop.GetCustomAttributes(typeof(ForeignKeyAttribute), false); | |
if (foreignKeyAttributes.Any()) | |
{ | |
var lookup = foreignKeyAttributes.First().Name; | |
if (lookup != null && prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) | |
{ | |
aggregate = aggregate.Lookup<T, T>(lookup, prefix + prop.Name, "_id", prefix + lookup); | |
Debug.WriteLine(aggregate.ToString()); | |
} | |
else if (lookup != null) | |
{ | |
aggregate = aggregate.Lookup<T, BsonDocument>(lookup, prefix + prop.Name, "_id", prefix + lookup).Unwind(x => x[prefix + lookup], new AggregateUnwindOptions<T> { PreserveNullAndEmptyArrays = true }); | |
Debug.WriteLine(aggregate.ToString()); | |
} | |
} | |
if (prop.PropertyType.IsClass) | |
{ | |
aggregate = this.BuildAggregate(aggregate, prop.PropertyType, prop.PropertyType.Name + "."); | |
} | |
} | |
return aggregate; | |
} | |
public void Start() | |
{ | |
var aggregate = this.personCollection.Aggregate(); | |
aggregate = this.BuildAggregate(aggregate, typeof(Person), ""); | |
List<Person> personList = aggregate.ToList(); | |
if (!personList.Any()) | |
{ | |
Console.WriteLine("No records yet"); | |
} | |
else | |
{ | |
foreach (var personResult in personList) | |
{ | |
Console.WriteLine($"Person {personResult.FirstName} has a Pet named {personResult.Pet.Name} with an address of {personResult.Pet.Address.Street}"); | |
} | |
} | |
Console.WriteLine("Press any key to write to mongo"); | |
Console.ReadKey(); | |
var count = personList.Count; | |
var firstEstate = new RealEstate() { Value = RandomNumber, SizeInSquareMeters = RandomNumber }; | |
this.realEstateCollection.InsertOne(firstEstate); | |
var secondEstate = new RealEstate() { Value = RandomNumber, SizeInSquareMeters = RandomNumber }; | |
this.realEstateCollection.InsertOne(secondEstate); | |
var address = new Address { City = RandomString(count, 5), HouseNumber = RandomString(count, 5), Street = RandomString(count, 5) }; | |
this.addressCollection.InsertOne(address); | |
var petAddress = new Address { City = RandomString(count, 5), HouseNumber = RandomString(count, 5), Street = RandomString(count, 5) }; | |
this.addressCollection.InsertOne(petAddress); | |
var pet = new Pet { Name = RandomString(count, 5), DoB = DateTime.Now, AddressId = petAddress.Id }; | |
this.petCollection.InsertOne(pet); | |
var person = new Person | |
{ | |
RealEstateIds = new List<BsonObjectId> | |
{ | |
new BsonObjectId(new ObjectId(firstEstate.Id)), | |
new BsonObjectId(new ObjectId(secondEstate.Id)) | |
}, | |
FirstName = RandomString(count, 5), | |
DoB = DateTime.Now, | |
AddressId = address.Id, | |
PetId = pet.Id | |
}; | |
this.personCollection.InsertOne(person); | |
Console.WriteLine("Wrote new records to mongo"); | |
Console.WriteLine("Press any key to exit"); | |
Console.ReadKey(); | |
} | |
private static readonly Random random = new Random(); | |
private static string RandomString(int prefix, int length) | |
{ | |
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
return prefix + "_" + new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); | |
} | |
private static int RandomNumber => random.Next(0, 50000); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple, yet working perfectly! Thanks for sharing!
Just had to change a little bit. In my mongodb, the collection names are in lowercase. So i've changed to the following:
aggregate = aggregate.Lookup<T, T>(lookup.ToLower(), prefix + prop.Name, "_id", prefix + lookup);
and
aggregate = aggregate.Lookup<T, BsonDocument>(lookup.ToLower(), prefix + prop.Name, "_id", prefix + lookup).Unwind(x => x[prefix + lookup], new AggregateUnwindOptions < T > { PreserveNullAndEmptyArrays = true });
Note the .toLower() on lookup variable