Skip to content

Instantly share code, notes, and snippets.

@Skamiplan
Last active July 9, 2020 19:33
Show Gist options
  • Save Skamiplan/45a709939dcb04795a77b3937f5f797c to your computer and use it in GitHub Desktop.
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.
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);
}
}
@Cafnio
Copy link

Cafnio commented Jul 9, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment