Created
July 17, 2020 14:34
-
-
Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.
Couchbase Mapping Idea
This file contains hidden or 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 System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using Couchbase.Lite; | |
using Couchbase.Lite.Mapping; | |
using FluentAssertions; | |
using NUnit.Framework; | |
// ReSharper disable TailRecursiveCall | |
// because the recursive call is easier to read ;) | |
namespace SomeMagicalNamespace | |
{ | |
[TestFixture] | |
public class CouchbaseMappingTests | |
{ | |
private readonly IMappingProvider _mappingProvider; | |
public CouchbaseMappingTests() | |
{ | |
_mappingProvider = new MappingProvider(); | |
MappingProvider.Configure(opts => | |
{ | |
opts.AddMapping<PersonMap>() | |
.AddMapping<AddressMap>() | |
.AddMapping<PetMap>(); | |
}); | |
} | |
[Test] | |
public void ShouldMapComplexObject() | |
{ | |
// arrange | |
var person = PersonFixture.JohnSmith; | |
// act | |
var dictionaryObject = person.ToMutableDocument(); | |
// assert | |
dictionaryObject["firstName"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.FirstName); | |
dictionaryObject["lastName"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.LastName); | |
dictionaryObject["address"]["street"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.Address.Street); | |
dictionaryObject["address"]["postalCode"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.Address.PostalCode); | |
} | |
[Test] | |
public void ShouldMapBasicObject() | |
{ | |
// arrange | |
var pet = PetFixture.Fido; | |
// act | |
var dictionaryObject = pet.ToMutableDocument(); | |
// assert | |
dictionaryObject["breed"] | |
.String | |
.Should() | |
.Be(PetFixture.Fido.Breed); | |
dictionaryObject["name"] | |
.String | |
.Should() | |
.Be(PetFixture.Fido.Name); | |
} | |
[Test] | |
public void ShouldMapBasicObjectWithMap() | |
{ | |
// arrange | |
var map = _mappingProvider.GetMap<Pet>(); | |
var pet = PetFixture.Fido; | |
// act | |
var dictionaryObject = pet.ToMutableDocument(map); | |
// assert | |
dictionaryObject["b"] | |
.String | |
.Should() | |
.Be(PetFixture.Fido.Breed); | |
dictionaryObject["n"] | |
.String | |
.Should() | |
.Be(PetFixture.Fido.Name); | |
} | |
[Test] | |
public void ShouldMapBasicDictionary() | |
{ | |
// arrange | |
var dict = new MutableDocument(new Dictionary<string, object> | |
{ | |
{"breed", PetFixture.Fido.Breed}, | |
{"name", PetFixture.Fido.Name} | |
}); | |
// act | |
var pet = dict.ToObject<Pet>(); | |
// assert | |
pet.Name.Should() | |
.Be(PetFixture.Fido.Name); | |
pet.Breed.Should() | |
.Be(PetFixture.Fido.Breed); | |
} | |
[Test] | |
public void ShouldMapBasicDictionaryWithMap() | |
{ | |
// arrange | |
var dict = new MutableDocument(new Dictionary<string, object> | |
{ | |
{"b", PetFixture.Fido.Breed}, | |
{"n", PetFixture.Fido.Name} | |
}); | |
// act | |
var pet = dict.ToObject<Pet>(); | |
// assert | |
pet.Name.Should() | |
.Be(PetFixture.Fido.Name); | |
pet.Breed.Should() | |
.Be(PetFixture.Fido.Breed); | |
} | |
[Test] | |
public void ShouldMapBasicDictionaryWithAttributeMap() | |
{ | |
// arrange | |
var dict = new MutableDocument(new Dictionary<string, object> | |
{ | |
{"Mk", VehicleFixture.ToyotaTundra.Make}, | |
{"Mdl", VehicleFixture.ToyotaTundra.Model} | |
}); | |
// act | |
var vehicle = dict.ToObject<Vehicle>(); | |
// assert | |
vehicle.Make.Should() | |
.Be(VehicleFixture.ToyotaTundra.Make); | |
vehicle.Model.Should() | |
.Be(VehicleFixture.ToyotaTundra.Model); | |
} | |
[Test] | |
public void ShouldMapComplexObjectWithMap() | |
{ | |
// arrange | |
var map = _mappingProvider.GetMap<Person>(); | |
var person = PersonFixture.JohnSmith; | |
// act | |
var dictionaryObject = person.ToMutableDocument(map); | |
// assert | |
dictionaryObject["fn"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.FirstName); | |
dictionaryObject["ln"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.LastName); | |
dictionaryObject["addr"]["st"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.Address.Street); | |
dictionaryObject["addr"]["pc"] | |
.Value | |
.Should() | |
.Be(PersonFixture.JohnSmith.Address.PostalCode); | |
} | |
[Test] | |
public void ShouldMapComplexDictionaryWithMap() | |
{ | |
throw new NotImplementedException(); | |
} | |
[Test] | |
public void ShouldMapComplexDictionary() | |
{ | |
// arrange | |
var dict = new MutableDocument(new Dictionary<string, object> | |
{ | |
{"firstName", PersonFixture.JohnSmith.FirstName}, | |
{"lastName", PersonFixture.JohnSmith.LastName}, | |
{ | |
"address", new Dictionary<string, object> | |
{ | |
{"street", PersonFixture.JohnSmith.Address.Street}, | |
{"postalCode", PersonFixture.JohnSmith.Address.PostalCode} | |
} | |
} | |
}); | |
// act | |
var person = dict.ToObject<Person>(); | |
// assert | |
person.FirstName.Should() | |
.Be(PersonFixture.JohnSmith.FirstName); | |
person.LastName.Should() | |
.Be(PersonFixture.JohnSmith.LastName); | |
person.Address.Street.Should() | |
.Be(PersonFixture.JohnSmith.Address.Street); | |
person.Address.PostalCode.Should() | |
.Be(PersonFixture.JohnSmith.Address.PostalCode); | |
} | |
} | |
public interface IMappingProvider | |
{ | |
IPropertyNameConverter GetMap<T>() where T : class; | |
} | |
public sealed class MappingProvider : IMappingProvider | |
{ | |
private static MappingOptions __mappingOptions; | |
public static void Configure(Action<MappingOptions> action) | |
{ | |
if (__mappingOptions != null) | |
{ | |
throw new InvalidOperationException("You can only configure your mapping one time."); | |
} | |
var provider = new MappingProvider(); | |
__mappingOptions = new MappingOptions(provider); | |
action.Invoke(__mappingOptions); | |
} | |
public IPropertyNameConverter GetMap<T>() where T : class => GetMap(typeof(T).Name); | |
public IPropertyNameConverter GetMap(string objectPropertyName) | |
{ | |
if (__mappingOptions is null) | |
{ | |
throw new InvalidOperationException("You must configure your mapping before trying to acquire a map."); | |
} | |
return __mappingOptions.GetMap(objectPropertyName); | |
} | |
} | |
public sealed class MappingOptions | |
{ | |
private readonly MappingProvider _mappingProvider; | |
private readonly Dictionary<string, Mapping> _mappings = new Dictionary<string, Mapping>(); | |
public MappingOptions(MappingProvider mappingProvider) => _mappingProvider = mappingProvider; | |
public MappingOptions AddMapping<T>() where T : Mapping | |
{ | |
var mapping = (T) Activator.CreateInstance(typeof(T), _mappingProvider); | |
_mappings[mapping.ObjectTypeName] = mapping; | |
return this; | |
} | |
public IPropertyNameConverter GetMap<T>() => GetMap(typeof(T).Name); | |
public IPropertyNameConverter GetMap(string objectTypeName) => _mappings[objectTypeName]; | |
} | |
public abstract class Mapping : IPropertyNameConverter | |
{ | |
protected Mapping(MappingProvider mappingProvider) => _mappingProvider = mappingProvider; | |
protected readonly HashSet<(string objectPropetyName, string documentPropertyName)> PropertyMap = new HashSet<(string objectPropetyName, string documentPropertyName)>(); | |
private readonly Dictionary<(string objectPropetyName, string documentPropertyName), Mapping> _childMaps = new Dictionary<(string objectPropetyName, string documentPropertyName), Mapping>(); | |
public string ObjectTypeName { get; protected set; } | |
public string Convert(string val) | |
{ | |
var propertyMap = PropertyMap.FirstOrDefault(x => x.objectPropetyName == val); | |
if (propertyMap != default) | |
{ | |
return propertyMap.documentPropertyName; | |
} | |
// mapping must be on a complex object | |
var childMapping = _mappingProvider.GetMap(val); | |
// how do we access complex child objects? | |
throw new NotImplementedException(); | |
} | |
private readonly MappingProvider _mappingProvider; | |
} | |
public abstract class Mapping<T> : Mapping where T : class | |
{ | |
protected Mapping(MappingProvider mappingProvider) : base(mappingProvider) => ObjectTypeName = typeof(T).Name; | |
protected void MappingFor(Expression<Func<T, object>> expression, string propertyName) | |
{ | |
var memberName = GetMemberName(expression.Body); | |
PropertyMap.Add((memberName, propertyName)); | |
} | |
private static string GetMemberName(Expression expression) | |
{ | |
switch (expression.NodeType) | |
{ | |
case ExpressionType.MemberAccess: return ((MemberExpression) expression).Member.Name; | |
case ExpressionType.Convert: return GetMemberName(((UnaryExpression) expression).Operand); | |
default: throw new NotSupportedException(expression.NodeType.ToString()); | |
} | |
} | |
} | |
public class PersonMap : Mapping<Person> | |
{ | |
public PersonMap(MappingProvider provider) : base(provider) | |
{ | |
MappingFor(x => x.FirstName, "fn"); | |
MappingFor(x => x.LastName, "ln"); | |
MappingFor(x => x.Address, "addr"); | |
} | |
} | |
public class AddressMap : Mapping<Address> | |
{ | |
public AddressMap(MappingProvider provider) : base(provider) | |
{ | |
MappingFor(x => x.Street, "st"); | |
MappingFor(x => x.PostalCode, "pc"); | |
} | |
} | |
public class PetMap : Mapping<Pet> | |
{ | |
public PetMap(MappingProvider provider) : base(provider) | |
{ | |
MappingFor(x => x.Breed, "b"); | |
MappingFor(x => x.Name, "n"); | |
} | |
} | |
} |
This file contains hidden or 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
public sealed class Person | |
{ | |
public string FirstName { get; set; } | |
public string LastName { get; set; } | |
public Address Address { get; set; } | |
} | |
public sealed class Address | |
{ | |
public string Street { get; set; } | |
public string PostalCode { get; set; } | |
} | |
public static class PersonFixture | |
{ | |
public static Person JohnSmith = new Person | |
{ | |
FirstName = "John", | |
LastName = "Smith", | |
Address = new Address | |
{ | |
Street = "123 Elm", | |
PostalCode = "A1A 1A1" | |
} | |
}; | |
} |
This file contains hidden or 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
public class PetFixture | |
{ | |
public static Pet Fido = new Pet | |
{ | |
Breed = "Shitzu", | |
Name = "Fido" | |
}; | |
} | |
public class Pet | |
{ | |
public string Breed { get; set; } | |
public string Name { get; set; } | |
} |
This file contains hidden or 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
public class VehicleFixture | |
{ | |
public static readonly Vehicle ToyotaTundra = new Vehicle | |
{ | |
Make = "Toyota", | |
Model = "Tundra" | |
}; | |
} | |
public sealed class Vehicle | |
{ | |
[MappingPropertyName("Mk")] public string Make { get; set; } | |
[MappingPropertyName("Mdl")] public string Model { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment