MongoDB Composite Key: InvalidOperationException: {document}.Identity is not supported

Viewed 2215

I am having issues with hydrating a class which consists of a composite ID which in turn has a base class, I am getting an error saying InvalidOperationException: {document}.Identity is not supported.

The class i am trying to write to the database is below:

public class Product : IEntity<Product>
{
    public readonly Sku Sku;
    public string Name { get; private set; }
    public string Description { get; private set; }
    public bool IsArchived { get; private set; }
    public Identity<Product> Identity => Sku;

    public Product(Sku sku, string name, bool isArchived)
    {
        Sku = sku;
        Name = name;
        IsArchived = isArchived;
    }
}

public interface IEntity<T>
{
    Identity<T> Identity { get; }
}

In turn has an ID Sku which is a class formed of the below composite values (VendorId and a local Value within Sku):

public class Sku : Identity<Product>
{
    public readonly VendorId VendorId;
    public readonly string Value;

    public Sku(VendorId vendorId, string value)
    {
        VendorId = vendorId;
        Value = value;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {VendorId, Value};
    }
}

public class VendorId : Identity<Vendor>
{
    public readonly string Value;

    public VendorId(string value)
    {
        Value = value;
    }

    protected override IEnumerable<object> GetIdentityComponents()
    {
        return new object[] {Value};
    }
}

I have a base class for my entities Identity which i use in my DDD libraries, essentially the ToString() output here could be used as the ID if this would simplify things:

public abstract class Identity<T> : IEquatable<Identity<T>>
{
    public override bool Equals(object obj) { /* snip */ }
    public bool Equals(Identity<T> other) { /* snip */ }
    public override int GetHashCode() { /* snip */ }

    public override string ToString()
    {
        var id = string.Empty;

        foreach (var component in GetIdentityComponents())
        {
            if (string.IsNullOrEmpty(id))
                id = component.ToString(); // first item, dont add a divider
            else
                id += "." + component;
        }

        return id;
    }

    protected abstract IEnumerable<object> GetIdentityComponents();
}

I register the mappings on app start:

// rehydrate readonly properties via matched constructor
// https://stackoverflow.com/questions/39604820/serialize-get-only-properties-on-mongodb
ConventionRegistry
    .Register(nameof(ImmutablePocoConvention), new ConventionPack { new ImmutablePocoConvention() }, _ => true);

BsonClassMap.RegisterClassMap<Product>(cm =>
{
    cm.AutoMap();
    cm.MapIdMember(c => c.Sku);
});

BsonClassMap.RegisterClassMap<Vendor>(cm =>
{
    cm.AutoMap();
    cm.MapIdMember(c => c.Id);
});

However when i go and write, i get InvalidOperationException: {document}.Identity is not supported.

// my respositoru method
public void Upsert<T>(T entity) where T : IEntity<T>
{
    this.Database
        .GetCollection<T>(product.GetType().FullName)()
        .ReplaceOneAsync(x=>x.Identity.Equals(entity.Identity), entity, new UpdateOptions() {IsUpsert = true})
        .Wait();
}

var product = new Product(new Sku(new VendorId("dell"), "12434" ),"RAM", false );
myProductRepo.Upsert(product);

Not sure if this is now overly complicated by me persisting direct from my entities layer (or if i just use an automapper and simpler POCO)... or if I am missing some mapping directives.

Appreciate any help or pointers.

2 Answers
Related