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

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.

I was looking at the hydration via constructor post which is done through GetProperties.
So public readonly Sku Sku; doesn't show up through classMap.ClassType.GetTypeInfo().GetProperties(_bindingFlags) because it is only can be accessed as member field.
You can change it to public Sku Sku { get; } so it is hydrated through constructor via GetProperties and change all the readonly fields (Sku - VendorId, Value & VendorId - Value fields) to have property getter method.
Also, You've to add cm.MapProperty(c => c.Identity) so x=>x.Identity.Equals(entity.Identity) can be serialized when used as expression because Identity cannot be hydrated and registered through ImmutablePocoConventionas it is not a constructor arg when automap logic runs.
Code changes:
public class Sku : Identity<Product>
{
public VendorId VendorId { get; }
public string Value { get; }
}
public class VendorId : Identity<Vendor>
{
public string Value { get; }
}
BsonClassMap.RegisterClassMap<Product>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Sku);
cm.MapProperty(c => c.Identity);
});

Here is the code i used:
public class ProductMongoRepository : IProductRepository
{
public ICollection<Product> SearchBySkuValue(string sku)
{
return ProductsMongoDatabase.Instance.GetEntityList<Product>();
}
public Product GetBySku(Sku sku)
{
var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();
return collection.Find(x => x.Sku.Equals(sku)).First();
}
public void SaveAll(IEnumerable<Product> products)
{
foreach (var product in products)
{
Save(product);
}
}
public void Save(Product product)
{
var collection = ProductsMongoDatabase.Instance.GetCollection<Product>();
collection
.ReplaceOneAsync(
x => x.Sku.Equals(product.Sku),
product,
new UpdateOptions() { IsUpsert = true })
.Wait();
}
}
Setting up the mapping here and support for readonly fields via constructor, for more complex scenarios and manual POCO mapping we could use BsonSerializer.RegisterSerializer(typeof(DomainEntityClass), new CustomerSerializer());
public sealed class ProductsMongoDatabase : MongoDatabase
{
private static volatile ProductsMongoDatabase instance;
private static readonly object SyncRoot = new Object();
private ProductsMongoDatabase()
{
BsonClassMap.RegisterClassMap<Sku>(cm =>
{
cm.MapField(c => c.VendorId);
cm.MapField(c => c.SkuValue);
cm.MapCreator(c => new Sku(new VendorId(c.VendorId.VendorShortname), c.SkuValue));
});
BsonClassMap.RegisterClassMap<VendorId>(cm =>
{
cm.MapField(c => c.VendorShortname);
cm.MapCreator(c => new VendorId(c.VendorShortname));
});
BsonClassMap.RegisterClassMap<Product>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Sku);
cm.MapCreator(c => new Product(c.Sku, c.Name, c.IsArchived));
});
BsonClassMap.RegisterClassMap<Vendor>(cm =>
{
cm.AutoMap();
cm.MapIdMember(c => c.Id);
cm.MapCreator(c => new Vendor(c.Id, c.Name));
});
}
public static ProductsMongoDatabase Instance
{
get
{
if (instance != null)
return instance;
lock (SyncRoot)
{
if (instance == null)
instance = new ProductsMongoDatabase();
}
return instance;
}
}
}
The above implementation (which is a singleton) derives from the below base (any queries or writes are done in the parent implementation):
public abstract class MongoDatabase
{
private readonly IConfigurationRepository _configuration;
private readonly IMongoClient Client;
private readonly IMongoDatabase Database;
protected MongoDatabase()
{
//_configuration = configuration;
var connection = "mongodb://host:27017";
var database = "test";
this.Client = new MongoClient();
this.Database = this.Client.GetDatabase(database);
}
public List<T> GetEntityList<T>()
{
return GetCollection<T>()
.Find(new BsonDocument()).ToList<T>();
}
public IMongoCollection<T> GetCollection<T>()
{
return this.Database.GetCollection<T>(typeof(T).FullName);
}
}
My Sku domain model:
public class Sku : Identity<Product>
{
public readonly VendorId VendorId;
public readonly string SkuValue;
public Sku(VendorId vendorId, string skuValue)
{
VendorId = vendorId;
SkuValue = skuValue;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {VendorId, SkuValue};
}
}
My Product domain model:
public class Product : IEntity<Product>
{
public readonly Sku Sku;
public string Name { get; private set; }
public bool IsArchived { get; private set; }
public Product(Sku sku, string name, bool isArchived)
{
Sku = sku;
Name = name;
IsArchived = isArchived;
}
public void UpdateName(string name)
{
Name = name;
}
public void UpdateDescription(string description)
{
Description = description;
}
public void Archive()
{
IsArchived = true;
}
public void Restore()
{
IsArchived = false;
}
// this is used by my framework, not MongoDB
public Identity<Product> Identity => Sku;
}
My VendorID:
public class VendorId : Identity<Vendor>
{
public readonly string VendorShortname;
public VendorId(string vendorShortname)
{
VendorShortname = vendorShortname;
}
protected override IEnumerable<object> GetIdentityComponents()
{
return new object[] {VendorShortname};
}
}
Then i have my entity and identity types:
public interface IEntity<T>
{
Identity<T> Identity { get; }
}
public abstract class Identity<T> : IEquatable<Identity<T>>
{
private const string IdentityComponentDivider = ".";
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
if (ReferenceEquals(null, obj)) return false;
if (GetType() != obj.GetType()) return false;
var other = obj as Identity<T>;
return other != null && GetIdentityComponents().SequenceEqual(other.GetIdentityComponents());
}
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 += IdentityComponentDivider + component;
}
return id;
}
protected abstract IEnumerable<object> GetIdentityComponents();
public override int GetHashCode()
{
return HashCodeHelper.CombineHashCodes(GetIdentityComponents());
}
public bool Equals(Identity<T> other)
{
return Equals(other as object);
}
}

Related

looking to apply T for dictionary value

I have below OSM material data structure that is different than entities and i have methods inside entities which i am forming compatible OSM material using AddToOsm method
public class FenestrationMaterial : Material
{ }
public class StandardOpaqueMaterial : OpaqueMaterial
{ }
public class OpaqueMaterial : Material
{ }
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public Guid SurfaceTypeId { get; set; }
public IntendedSurfaceType SurfaceType { get; set; }
public List<Guid> LayerIds { get; set; }
public Construction AddToOsm(Model model, APIDbContext dbContext)
{
var construction = new Construction(model);
using var materials = new MaterialVector();
var fenestrationMaterialById = new Dictionary<Guid, FenestrationMaterial>();
var standardOpaqueMaterialById = new Dictionary<Guid, StandardOpaqueMaterial>();
var opaqueMaterialById = new Dictionary<Guid, OpaqueMaterial>();
foreach (var materialId in LayerIds.Where(i => i != default))
{
if (ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId) != default)
{
var OpaqueProjectMaterial = ProjectUtils.EntityById<OpaqueProjectMaterial>(dbContext, materialId);
materials.Add(
standardOpaqueMaterialById.GetOrCreate(OpaqueProjectMaterial.Id, () => OpaqueProjectMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId) != default)
{
var airGapMaterial = ProjectUtils.EntityById<AirGapMaterial>(dbContext, materialId);
materials.Add(
opaqueMaterialById.GetOrCreate(airGapMaterial.Id, () => airGapMaterial.AddToOsm(model))
);
continue;
}
if (ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId) != default)
{
var glazingMaterial = ProjectUtils.EntityById<GlazingMaterial>(dbContext, materialId);
materials.Add(
fenestrationMaterialById.GetOrCreate(glazingMaterial.Id, () => glazingMaterial.AddToOsm(model))
);
continue;
}
}
construction.setLayers(materials);
return construction;
}
}
and then i do have entities for airGapmaterial, OpaqueProjectMaterial and GlazingMaterial
public class AirGapMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
......
......
public OpaqueMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var airGapMaterial = new AirGap(model);
airGapMaterial.setName(this.Name);
.......
return airGapMaterial;
}
}
public class GlazingMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
.......
......
public FenestrationMaterial AddToOsm(Model model)
{
if (model is null)
{
throw new ArgumentNullException(nameof(model));
}
var glazingMaterialComplexModel = new StandardGlazing(model);
glazingMaterialComplexModel.setName(this.Name);
........
return glazingMaterialComplexModel;
}
}
Is there any way I can use generic variable in-place of FenestrationMaterial and StandardOpaqueMaterial in the initialization of dictionaries (fenestrationMaterialById, standardOpaqueMaterialById) and extract the below common methods into single one?
I am looking kind of like this if possible
var fenestrationMaterialById = new Dictionary<Guid, T>();
That took a bit longer then I expected. Turns out the ProjectUtils bit really does throw a wrench into the works. In the following I trimmed things down to just the core concepts, added types missing from your post and did some refactoring to try and stream line things as much as possible. There is still plenty of room for improvement.
I also had to make a ton of assumptions about the underlying data and undocumented components. It looks to me like it would do what you're looking for but its certainly possible that I've made an assumption that is breaking on your end. I also tried to follow the pattern with the Util and DbContext usage, however, to me, those look pretty weak and should probably be reworked.
The core section you wanted to refactor is now:
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
And here's the rest, it should compile.
public class Model { }
public class Construction
{
public Construction(Model model, MaterialVector materials)
{
SetLayers(materials);
}
internal void SetLayers(MaterialVector materials)
{
}
}
public interface ISourceOfData
{
public string Name { get; set; }
}
public interface IIdentity<T>
{
[Key]
public Guid Id { get; set; }
}
public class IntendedSurfaceType { }
public class APIDbContext { }
public class KeyAttribute : Attribute { }
public class MaterialVector : IDisposable
{
public void Dispose()
{
}
internal void Add(IMaterial p)
{
}
}
public interface IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData i);
}
public class MaterialTypeResolver : IMaterialTypeResolver
{
public IMaterialSource ResolveType(SourceData sourceData)
{
//Problem here - I don't know how your ProjectUtils.EntityById works internally
//So I'm guessing here that it uses a factory pattern
//this method needs to return an IMaterialSource...
//GlazingMaterialSource
//AirGapMaterialSource
//ect ect
//for a given set of database values
//essentially you need something coming from the database to tell the code
//which type to create, we pass that the data base values
//and it passes them later to the IMaterial in the AddToOsm call
//so the IMaterial implementation can do whatever custom work
return new OpaqueProjectMaterialSource(sourceData);
}
}
public class ProjectUtils
{
private IMaterialTypeResolver _MaterialTypeResolver;
public ProjectUtils(IMaterialTypeResolver materialTypeResolver)
{
_MaterialTypeResolver = materialTypeResolver;
}
public T EntityById<T>(APIDbContext dbContext, Guid materialId) where T : new()
//here you'd use the dbContext to populated this
//though this one at a time stuff is pretty inefficient
//it should be done as a single query if possible
=> new T();
public IMaterialSource ResolveToMaterialSource(SourceData i) => _MaterialTypeResolver.ResolveType(i);
}
public interface IMaterial
{
public string Name { get; set;}
}
public abstract class MaterialBase : IMaterial
{
public string Name {get;set;}
public MaterialBase(Model model, ISourceOfData source)
{
this.Name = source.Name;
}
}
public class AirGapMaterial : MaterialBase
{
public AirGapMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardGlazingMaterial : MaterialBase
{
public StandardGlazingMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class FenestrationMaterial : MaterialBase
{
public FenestrationMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class StandardOpaqueMaterial : OpaqueMaterial
{
public StandardOpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class OpaqueMaterial : MaterialBase
{
public OpaqueMaterial(Model model, ISourceOfData source) : base(model, source)
{
}
}
public class SurfaceConstruction : IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
private ProjectUtils _projectUtils;
public SurfaceConstruction(ProjectUtils projectUtils)
{
_projectUtils = projectUtils;
}
public Construction AddToOsm(APIDbContext dbContext, Model model, IEnumerable<Guid> ids)
{
if(model is null) throw new ArgumentNullException(nameof(model));
var materialSources = ids.Where(i => i != default)
.Select(i => _projectUtils.EntityById<SourceData>(dbContext, i))
.Select(i => _projectUtils.ResolveToMaterialSource(i));
using var materialVector = new MaterialVector();
foreach (var materialSource in materialSources)
{
materialVector.Add(materialSource.AddToOsm(model));
}
return new Construction(model, materialVector);
}
}
public interface IMaterialSource
{
public IMaterial AddToOsm(Model model);
}
public class SourceData : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
//add all the database loaded properties here
}
public abstract class MaterialSourceBase : IMaterialSource
{
protected SourceData SourceData {get;set;}
protected MaterialSourceBase(SourceData sourceData)
{
SourceData = sourceData;
}
public abstract IMaterial AddToOsm(Model model);
}
//So at this point you could generalize all of this down to a single
//factory pattern backed function as all we care about are
//given a set of database values, give me an IMaterial
public class OpaqueProjectMaterialSource : MaterialSourceBase
{
public OpaqueProjectMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardOpaqueMaterial(model, base.SourceData);
}
public class AirGapMaterialSource : MaterialSourceBase
{
public AirGapMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new AirGapMaterial(model, base.SourceData);
}
public class GlazingMaterialSource : MaterialSourceBase
{
public GlazingMaterialSource(SourceData sourceData) : base(sourceData)
{
}
public override IMaterial AddToOsm(Model model) => new StandardGlazingMaterial(model, base.SourceData);
}

MongoDB: Deserializing complex object returns NULL

Using C# Mongo DB Driver version, 2.4.4 and I try to deserialize a complex class. It has been stored correctly in the MongoDB, but when being deserialized it is null.
This is my datamodel:
public abstract class Document
{
public DocumentId Id { get; private set; }
public DocumentProperty TestProperty { get; private set; }
protected Document() { }
public void SetId(string value)
{
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("value");
Id = new DocumentId(value);
}
public void AddProperty(DocumentProperty property)
{
if (property == null) throw new ArgumentNullException("property");
TestProperty = property;
}
}
public class DocumentId
{
public string Value { get; }
public DocumentId(string value)
{
Value = value;
}
}
public abstract class DocumentProperty
{
public string Value { get; }
protected DocumentProperty()
{
}
protected DocumentProperty(string value)
{
Value = value;
}
}
public class TitleDocumentProperty : DocumentProperty
{
public TitleDocumentProperty() { }
public TitleDocumentProperty(string value) : base(value)
{
}
}
This is my mapping code:
public class MongoClassMapper
{
public static void InitializeMap()
{
BsonClassMap.RegisterClassMap<DocumentProperty>(map =>
{
map.MapProperty(property => property.Value).SetDefaultValue("123");
map.SetIsRootClass(true);
});
BsonClassMap.RegisterClassMap<TitleDocumentProperty>(map =>
{
});
BsonClassMap.RegisterClassMap<Document>(map =>
{
map.MapProperty(document => document.Id);
map.MapProperty(document => document.TestProperty);
map.SetIsRootClass(true);
});
}
}
This is my methods for adding and retrieving data from Mongo:
public async Task<string> Add(Document document)
{
await _documents.InsertOneAsync(document);
return document.Id.Value;
}
public async Task<Document> Get(DocumentId id)
{
var mongoDocuments = await _documents.Find(document => document.Id == id)
.ToListAsync();
return mongoDocuments.SingleOrDefault();
}
This is the code that I use for testing:
private static MongoCache _cache;
static void Main(string[] args)
{
MongoClassMapper.InitializeMap();
_cache = new MongoCache(ConfigurationManager.ConnectionStrings["connName"].ConnectionString, "dbName");
string id = ItShouldCreateRecord().Result;
var doc = ItShouldGetRecord(id).Result;
}
private static Task<string> ItShouldCreateRecord()
{
//Arrange
Document document = new FakeDocument();
document.SetId(Guid.NewGuid().ToString());
document.AddProperty(new TitleDocumentProperty("this is a title"));
//Act
string id = _cache.Add(document).Result;
//Assert
//Assert.NotEmpty(id);
return Task.FromResult(id);
}
private static Task<Document> ItShouldGetRecord(string id)
{
//Arrange
//Act
return _cache.Get(new DocumentId(id));
//Assert
//Assert.NotNull(doc);
}
}
[BsonIgnoreExtraElements]
public class FakeDocument : Document
{
}
I expected that when I retrieve the document (using _cache.Get()) from the DB, that the property TestProperty would have an actual value. Currently, it is NULL.
The problem is the absence of a setter on your Value property which will cause MongoDB to not deserialize this property:
public abstract class DocumentProperty
{
public string Value { get; /* no setter*/ }
}
You can fix this by simply adding the setter (using any accessibility setting, even private works):
public abstract class DocumentProperty
{
public string Value { get; /* private if you want */ set; }
}
I have captured a JIRA issue and a pull request to get that fixed.

Transaction script implementation

Could you please advice me which of these two (if any) approaches to Transaction Script patter is correct?
Basically I need to implement "pure" TS, which is not to bend it in some convenient way. And the question is when I get data from Table Data Gateway shall I store them, for different parts (logical part for sake of clarity) of transaction and use them where do I need, OR get data don't store them but directly use them and if the same data are needed again call table gateway and ask for it.
Example: http://pastebin.com/hGCgrfEs
Thanks. Hopefully it makes sense if not let me know :)
The TSDoSomething2 class is closer to a good solution, i propose this to implement a SOLID transcript implementation:
public class OrderBusiness : IAcceptVisitOrder
{
private readonly string _id;
private int _status;
public OrderBusiness(string id, int status)
{
_id = id;
_status = status;
}
public void ChangeStatus(int newStatus)
{
//validation logic....
_status = newStatus;
}
public void Accept(IOrderVisitor visitor)
{
if (visitor == null) throw new ArgumentNullException(nameof(visitor));
visitor.Visit(_id, _status);
}
}
public class Client
{
private readonly OrderApplicationService _service;
//Get from Service locator like ninject, etc
private readonly IReader<OrderBusiness, string> _reader = new OrderBusinessReader();
private readonly IOrderVisitor _visitor = new UpdateOrderVisitor();
public Client()
{
_service = new OrderApplicationService(_reader, _visitor);
}
public void Run(UpdateOrderCommand command)
{
_service.When(command);
}
}
public class UpdateOrderCommand
{
[Required]
public string Id { get; set; }
[Required]
public int Status { get; set; }
}
public class OrderApplicationService
{
private readonly IReader<OrderBusiness, string> _reader;
private readonly IOrderVisitor _visitor;
public OrderApplicationService(IReader<OrderBusiness, string> reader, IOrderVisitor visitor)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (visitor == null) throw new ArgumentNullException(nameof(visitor));
_reader = reader;
_visitor = visitor;
}
public void When(UpdateOrderCommand command)
{
var order = GetBusinessObject(command.Id);
order.ChangeStatus(command.Status);
order.Accept(_visitor);
}
private OrderBusiness GetBusinessObject(string id)
{
if (id == null) throw new ArgumentNullException(nameof(id));
return _reader.Read(id);
}
}
public interface IReader<out T, in TK>
{
T Read(TK id);
}
public interface IAcceptVisitOrder
{
void Accept(IOrderVisitor visitor);
}
public interface IOrderVisitor
{
void Visit(string id, int status);
}
public class OrderBusinessReader : IReader<OrderBusiness, string>
{
public OrderBusiness Read(string id)
{
//Read data from db with EF, ADO.NET, ETC
var status = 0;
return new OrderBusiness(id, status);
}
}
class UpdateOrderVisitor : IOrderVisitor
{
public void Visit(string id, int status)
{
//Persist to database, etc
}
}
Hope this helps!Regards!

Create a fixture for recursive data structure with AutoFixture

I'm working on a project where I have some recursive data structure and I want to create a fixture for it.
The data structure is XmlCommandElement, it has a single method ToCommand that converts XmlCommandElement to Command.
Each node on the tree can be a XmlCommandElement and/or XmlCommandPropertyElement.
Now, in order to test the behaviour of the method ToCommand I want to fetch XmlCommandElement with some arbitrary data.
I want to control the depth of the tree and the amount of instances of XmlCommandElement and/or XmlCommandPropertyElement per node.
So here is the code I'm using for the fixture:
public class XmlCommandElementFixture : ICustomization
{
private static readonly Fixture _fixture = new Fixture();
private XmlCommandElement _xmlCommandElement;
public int MaxCommandsPerDepth { get; set; }
public int MaxDepth { get; set; }
public int MaxPropertiesPerCommand { get; set; }
public XmlCommandElementFixture BuildCommandTree()
{
_xmlCommandElement = new XmlCommandElement();
var tree = new Stack<XmlCommandElementNode>();
tree.Push(new XmlCommandElementNode(0, _xmlCommandElement));
while (tree.Count > 0) {
var node = tree.Pop();
node.Command.Key = CreateRandomString();
node.Command.Properties = CreateProperties();
if (MaxDepth > node.Depth) {
var commands = new List<XmlCommandElement>();
for (var i = 0; i < MaxCommandsPerDepth; i++) {
var command = new XmlCommandElement();
tree.Push(new XmlCommandElementNode(node.Depth + 1, command));
commands.Add(command);
}
node.Command.Commands = commands.ToArray();
}
}
return this;
}
public void Customize(IFixture fixture)
{
fixture.Customize<XmlCommandElement>(c => c.FromFactory(() => _xmlCommandElement)
.OmitAutoProperties());
}
private static string CreateRandomString()
{
return _fixture.Create<Generator<string>>().First();
}
private XmlCommandPropertyElement[] CreateProperties()
{
var properties = new List<XmlCommandPropertyElement>();
for (var i = 0; i < MaxPropertiesPerCommand; i++) {
properties.Add(new XmlCommandPropertyElement {
Key = CreateRandomString(),
Value = CreateRandomString()
});
}
return properties.ToArray();
}
private struct XmlCommandElementNode
{
public XmlCommandElementNode(int depth, XmlCommandElement xmlCommandElement)
{
Depth = depth;
Command = xmlCommandElement;
}
public XmlCommandElement Command { get; }
public int Depth { get; }
}
}
And this is how I'm using it:
xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {
MaxDepth = 2,
MaxCommandsPerDepth = 3,
MaxPropertiesPerCommand = 4
}.BuildCommandTree()).Create<XmlCommandElement>();
This works perfectly fine! but the issue I have with it is it isn't generic, the whole point of AutoFixture at least as far as I know is to avoid making specific fixtures.
So what I would really like to do is something like this (found it here but it doesn't work for me.):
var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new DepthThrowingRecursionBehavior(2));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandElement), 3));
fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandPropertyElement), 4));
xmlCommandElement = fixture.Create<XmlCommandElement>();
Here is all the code for reference:
Interfaces:
public interface ICommandCollection : IEnumerable<ICommand>
{
ICommand this[string commandName] { get; }
void Add(ICommand command);
}
public interface ICommandPropertyCollection : IEnumerable<ICommandProperty>
{
string this[string key] { get; }
void Add(ICommandProperty property);
}
public interface ICommandProperty
{
string Key { get; }
string Value { get; }
}
public interface ICommand
{
ICommandCollection Children { get; set; }
string Key { get; }
ICommandPropertyCollection Properties { get; }
}
public interface ICommandConvertible
{
ICommand ToCommand();
}
Classes:
public sealed class CommandPropertyCollection : ICommandPropertyCollection
{
private readonly IDictionary<string, ICommandProperty> _properties;
public CommandPropertyCollection()
{
_properties = new ConcurrentDictionary<string, ICommandProperty>();
}
public string this[string key]
{
get
{
ICommandProperty property = null;
_properties.TryGetValue(key, out property);
return property.Value;
}
}
public void Add(ICommandProperty property)
{
_properties.Add(property.Key, property);
}
public IEnumerator<ICommandProperty> GetEnumerator()
{
return _properties.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public sealed class CommandProperty : ICommandProperty
{
public CommandProperty(string key, string value)
{
Key = key;
Value = value;
}
public string Key { get; }
public string Value { get; }
}
public sealed class Command : ICommand
{
public Command(string key, ICommandPropertyCollection properties)
{
Key = key;
Properties = properties;
}
public ICommandCollection Children { get; set; }
public string Key { get; }
public ICommandPropertyCollection Properties { get; }
}
public class XmlCommandPropertyElement : ICommandPropertyConvertible
{
[XmlAttribute("key")]
public string Key { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
public ICommandProperty ToCommandProperty()
{
return new CommandProperty(Key, Value);
}
}
Finally, the class I'm trying to test is as follow:
public class XmlCommandElement : ICommandConvertible
{
[XmlArray]
[XmlArrayItem("Command", typeof(XmlCommandElement))]
public XmlCommandElement[] Commands { get; set; }
[XmlAttribute("key")]
public string Key { get; set; }
[XmlArray]
[XmlArrayItem("Property", typeof(XmlCommandPropertyElement))]
public XmlCommandPropertyElement[] Properties { get; set; }
public ICommand ToCommand()
{
ICommandPropertyCollection properties = new CommandPropertyCollection();
foreach (var property in Properties) {
properties.Add(property.ToCommandProperty());
}
ICommand command = new Command(Key, properties);
return command;
}
}
The test itself looks like this:
namespace Yalla.Tests.Commands
{
using Fixtures;
using FluentAssertions;
using Ploeh.AutoFixture;
using Xbehave;
using Yalla.Commands;
using Yalla.Commands.Xml;
public class XmlCommandElementTests
{
[Scenario]
public void ConvertToCommand(XmlCommandElement xmlCommandElement, ICommand command)
{
$"Given an {nameof(XmlCommandElement)}"
.x(() =>
{
xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {
MaxDepth = 2,
MaxCommandsPerDepth = 3,
MaxPropertiesPerCommand = 4
}.BuildCommandTree()).Create<XmlCommandElement>();
});
$"When the object is converted into {nameof(ICommand)}"
.x(() => command = xmlCommandElement.ToCommand());
"Then we need to have a root object with a key"
.x(() => command.Key.Should().NotBeNullOrEmpty());
"And 4 properties as its children"
.x(() => command.Properties.Should().HaveCount(4));
}
}
}
Thanks to Mark Seemann! the final solution looks like this:
public class RecursiveCustomization : ICustomization
{
public int MaxDepth { get; set; }
public int MaxElements { get; set; }
public void Customize(IFixture fixture)
{
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(MaxDepth));
fixture.RepeatCount = MaxElements;
}
}
And can be used like this:
xmlCommandElement = new Fixture().Customize(new RecursiveCustomization {
MaxDepth = 2,
MaxElements = 3
}).Create<XmlCommandElement>();
You can fairly easily create a small tree by changing the Fixture's recursion behaviour:
[Fact]
public void CreateSmallTree()
{
var fixture = new Fixture();
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth: 2));
var xce = fixture.Create<XmlCommandElement>();
Assert.NotEmpty(xce.Commands);
}
The above test passes.

Retrieving Navigation Properties with NHibernate Criteria

I think I'm just searching for the wrong terms, since I can't seem to find an answer to what I'm fairly sure is a simple question.
I have two classes/mappings (simplified);
public class Applications
{
public int Id {get; set;}
public string Name {get; set;}
public ApplicationType {get; set;}
public IEnumerable<ApplicationProperty> ApplicationProperties {get; set;}
}
public class ApplicationProperties
{
public int Id {get; set;}
public int Application_Id {get; set}
public string Value {get; set;}
}
I'm trying to build up a Criteria class in a (possibly misguided) attempt to make our code more readable/reusable, as follows;
public static class ApplicationsQuery
{
public static ICriteria GetQuery(ISession session)
{
return session.CreateCriteria(typeof(Application));
}
public static ICriteria WithType(this ICriteria crit, ApplicationType type)
{
crit.Add(
Restrictions.Eq(
Projections.ProjectionList().Add(
Projections.Property<Application>(a => a.ApplicationType)), type));
return crit;
}
public static ICriteria WithProperties(this ICriteria crit)
{
// What goes here?!
}
}
So that I can do something like
ICriteria query = ApplicationsQuery.GetQuery(session).WithType(ApplicationType.GameServer).WithProperties();
I've tried various things such as;
DetachedCriteria properties =
DetachedCriteria.For<Application>()
.SetProjection(Projections.Property<Application>(a => a.ApplicationProperties));
return crit.Add(Subqueries.Select(properties));
// Or this
return crit.SetFetchMode("ApplicationProperties", FetchMode.Eager);
But I'm unable to get the ApplicationProperties to be populated.
My test setup looks like this;
session.Save(new Application("Test Application 1", ApplicationType.GameServer), 1);
session.Save(new Application("Test Application 2", ApplicationType.Manager), , 2);
session.Save(new ApplicationProperty(1, "Test Property"), 1);
EDIT
Adding in the mappings as I get the feeling that there might be an issue with them.
public class ApplicationMapping : ClassMap<Application>
{
public ApplicationMapping()
{
Table("Applications");
Id(o => o.Id, "Application_Id").GeneratedBy.Assigned();
Map(o => o.Name).Column("Logical_Name");
Map(o => o.ApplicationType).Column("Application_Type").CustomType<ApplicationType>();
HasMany(o => o.ApplicationProperties).KeyColumn("Application_Id").ReadOnly().Cascase.None();
}
}
public class ApplicationPropertyMapping : ClassMap<ApplicationProperty>
{
public ApplicationPropertyMapping()
{
Table("Application_Properties");
Id(o => o.Id).GeneratedBy.Identity().Column("Property_Id");
Map(o => o.ApplicationId).Column("Application_Id");
Map(o => o.Value).Column("Property_Value");
}
}
Update: i stripped down the code
public class ApplicationsQueryBuilder
{
private static readonly IProjection ApplicationTypeProperty = Projections.Property<Application>(a => a.ApplicationType);
private readonly DetachedCriteria _query = DetachedCriteria.For<Application>();
private bool _withProperties;
private bool _filteredByCollection;
public ApplicationsQueryBuilder WithType(ApplicationType type)
{
_query.Add(Restrictions.Eq(ApplicationTypeProperty, type));
return this;
}
public ApplicationsQueryBuilder WithTypes(params ApplicationType[] types)
{
var or = Restrictions.Disjunction();
foreach (var type in types)
{
or.Add(Restrictions.Eq(ApplicationTypeProperty, type));
}
_query.Add(or);
return this;
}
public ApplicationsQueryBuilder WithProperties()
{
_withProperties = true;
return this;
}
public ApplicationsQueryBuilder WithProperty(string name)
{
_query.CreateCriteria("ApplicationProperties")
.Add(Restrictions.Eq("Name", name));
_filteredByCollection = true;
return this;
}
...
public ICriteria Create(ISession session)
{
if (_withProperties && _filteredByCollection)
{
_query.SetProjection(Projections.Id());
return session.CreateCriteria<Application>()
.SetFetchMode("ApplicationProperties", FetchMode.Eager);
.Add(Subqueries.PropertyIn("Id", _query));
}
else if (_withProperties)
{
return _query.GetExecutableCriteria(_session)
.SetFetchMode("ApplicationProperties", FetchMode.Eager);
}
else
{
return _query.GetExecutableCriteria(session);
}
}
}

Categories

Resources