Due to environment restrictions at work I'm implementing a rough Event Sourcing store in MongoDB. I'm trying to get a list of IClientEvents from Mongo like so:
var events = await _db.GetCollection<IClientEvent>("ClientEvents").FindAsync(c => c.ClientId == clientId);
I get the following exception when I run the above mentioned repository method:
Message: System.InvalidOperationException : {document}.ClientId is not supported.
The IClientEvent interface is defined as:
public interface IClientEvent
{
Guid Id { get; set; }
long TimeStamp { get; set; }
Guid ClientId { get; set; }
}
public class ClientChangedEvent : IClientEvent
{
public Guid Id { get; set; }
public long TimeStamp { get; set; }
public Guid ClientId { get; set; }
public IEnumerable<Change> Changes;
// ... other properties for the event
}
There will be many different event types stored into a single collection, all of which will implement IClientEvent. I want to just get, in a single call, all events that have occurred to a Client by clientId.
I have registered all of the concrete implementations of IClientEvent and even added a custom discriminator:
var clientEventsDiscriminator = new ClientEventsMongoDiscriminatorConvention();
BsonSerializer.RegisterDiscriminatorConvention(typeof(IClientEvent),clientEventsDiscriminator);
BsonClassMap.RegisterClassMap<ClientChangedEvent>();
BsonSerializer.RegisterDiscriminatorConvention(typeof(ClientChangedEvent), clientEventsDiscriminator);
I have even tried registering an ImpliedImplementationInterfaceSerializer as mentioned in this SO post but it throws an exception when I register the 2nd concrete implementation that I have already registered a serializer for IClientEvent.
Not sure where to go from here. Any help is greatly appreciated!
-- EDIT for more code:
Here is the full registration code:
var clientEventsDiscriminator = new ClientEventsMongoDiscriminatorConvention();
BsonSerializer.RegisterDiscriminatorConvention(typeof(IClientEvent),clientEventsDiscriminator);
clientEventsDiscriminator.AddEventType<ClientChangedEvent>();
BsonClassMap.RegisterClassMap<ClientChangedEvent>();
BsonSerializer.RegisterDiscriminatorConvention(typeof(ClientChangedEvent), clientEventsDiscriminator);
clientEventsDiscriminator.AddEventType<ClientAddedEvent>();
BsonClassMap.RegisterClassMap<ClientAddedEvent>();
BsonSerializer.RegisterDiscriminatorConvention(typeof(ClientAddedEvent), clientEventsDiscriminator);
Here is the Discriminator:
public class ClientEventsMongoDiscriminatorConvention : IDiscriminatorConvention
{
private Dictionary<string, Type> _eventTypes = new Dictionary<string, Type>();
public string ElementName => "_eventType";
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
{
return GetDiscriminatorValueForEventType(actualType);
}
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
{
var bookmark = bsonReader.GetBookmark();
bsonReader.ReadStartDocument();
if (!bsonReader.FindElement(ElementName))
{
throw new InvalidCastException($"Unable to find property '{ElementName}' in document. Cannot map to an EventType.");
}
var value = bsonReader.ReadString();
bsonReader.ReturnToBookmark(bookmark);
if (_eventTypes.TryGetValue(value, out var type))
{
return type;
}
throw new InvalidCastException($"The type '{value}' has not been registered with the '{nameof(ClientEventsMongoDiscriminatorConvention)}'.");
}
private string GetDiscriminatorValueForEventType(Type type)
{
var indexOfEventWord = type.Name.IndexOf("Event");
if (indexOfEventWord == -1)
{
return type.Name;
}
return type.Name.Substring(0, indexOfEventWord);
}
public void AddEventType<T>()
{
var discriminatorName = GetDiscriminatorValueForEventType(typeof(T));
_eventTypes.TryAdd(discriminatorName, typeof(T));
}
}
When running the code it doesn't appear to ever hit the GetActualType method of the discriminator.
I managed to get it to work by simply changing IClientEvent from an interface to an abstract class.
Related
Often i have a method where i want to return the error if something goes wrong, and instead of returning null, I want something less prone to errors at runtime and more easy to consume. Is there anything already done in .Net or maybe a nuget package?
Maybe have a constructor with optional parameters or object initializer would be enough?
This would have been the first approach but then every new Dto has to either have these Error property or inherit from a base class.
if (condition)
{
return new MyDto(null, error);
}
return new MyDto(someVariable, null);
So I've made this class to use a return type:
public class Optional<TObject> where TObject : class
{
public Optional(TObject? value)
{
Value = value;
}
public Optional(String error)
{
Error = error;
}
public TObject? Value { get; }
public String Error { get;} = String.Empty;
public Boolean IsError => !String.IsNullOrEmpty(Error);
}
I return it in the method:
if (condition)
{
return new Optional(error);
}
return new Optional(new MyDto(someVariable));
And then consume it like this:
var result = await myService.GetSomethingAsync();
if(result.IsError)
{
await DisplayAlert("error", result.Error, "Ok");
}
else
{
await DoSomethingElse(result.Value);
}
By creating a small class hierarchy, you could ensure that the Value property is only available when no error occurred
public abstract class Result
{
public virtual string Message => null;
public static Error Error(string message) => new Error(message);
public static Okay<T> Okay<T>(T value) where T : class => new Okay<T>(value);
}
public class Error : Result
{
public Error(string errorMessage) => Message = errorMessage;
override public string Message { get; }
}
public class Okay<T> : Result
where T : class
{
public Okay(T value) => Value = value;
public T Value { get; }
}
Usage
Result result = Result.Error("Something went wrong");
// OR
Result result = Result.Okay(new MyDto(someVariable));
if (result is Okay<MyDto> dtoResult) {
Console.WriteLine(dtoResult.Value);
} else {
Console.WriteLine(result.Message);
}
Or by using a recursive pattern, we can retrieve the value into a variable directly
if (result is Okay<MyDto> { Value: var dto }) {
Console.WriteLine(dto);
} else {
Console.WriteLine(result.Message);
}
Note that I have declared the Message property in the abstract base class Result, so that you don't have to cast to the Error type to get the message.
I used null as defualt value for the error message, as it allows us to write
Console.Writeline(result.Message ?? "okay");
This OneOf recommendation you got looks promising. I will personally have a look at it later.
What I do with my services is to standardize the result they return by using a SvcResult class or an inherited class.
Example:
public class SvcResult
{
public List<Error> Errors { get; } // Error is a class of my own. Add set; if deserialization is needed.
public bool Success { get; } // Add set; if deserialization is needed.
// Then parameterless constructor for a successful result.
// Then parameterized constructor to receive errors for a failed result.
}
That is the class for side-effect service calling. If The service returns data, I derive from the above to create DataSvcResult:
public class DataSvcResult<TResult> : SvcResult
{
public TResult Data { get; }
// Add constructor that receives TResult for a successful object result.
// Expose base class constructor that takes errors.
}
Basically that's what I do. But that OneOf thing, though. Looks super intersting.
I have two functions one for write and one for read when i try to use the read function first i get this error:
An error occurred while deserializing the Message property of class DDSRecorder.MessageContainer: Instances of abstract classes cannot be created
Here is what i dont get, if i use the write first at least once then the read works fine. i dont understand what happens in the background that makes it ok to initialize abstract class if we used it once to write.
Adding the map for it didn't resolve the problem:
if (BsonClassMap.IsClassMapRegistered(typeof(MessageContainer)))
{
BsonClassMap.RegisterClassMap<MessageBase>(cm =>
{
cm.AutoMap();
cm.SetIsRootClass(true);
});
}
Here is the class i am using for the mongo collection.
[BsonIgnoreExtraElements(true)]
public class MessageContainer
{
[BsonId]
public ObjectId Id { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime TimeStamp { get; set; }
[BsonElement]
public string MessageType { get; set; }
public MessageBase Message { get; set; }
[BsonConstructor]
public MessageContainer()
{
}
[BsonConstructor]
public MessageContainer(MessageBase message)
{
Message = message ?? throw new ArgumentNullException(nameof(message));
TimeStamp = DateTime.UtcNow;
MessageType = message.GetType().Name;
}
[BsonConstructor]
public MessageContainer(DateTime timeStamp, string messageType, MessageBase message)
{
TimeStamp = timeStamp;
MessageType = messageType ?? throw new ArgumentNullException(nameof(messageType));
Message = message ?? throw new ArgumentNullException(nameof(message));
}
}
And the abstract class inside:
public abstract class MessageBase
{
protected MessageBase();
public MessageBase CreateCopy();
}
Example of write method:
public bool Write(MessageContainer message)
{
if (message != null && _mongoCollection != null)
{
try
{
if (!BsonClassMap.IsClassMapRegistered(typeof(MessageContainer)))
{
BsonClassMap.RegisterClassMap<MessageContainer>();
BsonClassMap.RegisterClassMap<MessageBase>(cm =>
{
cm.AutoMap();
cm.SetIsRootClass(true);
});
}
_mongoCollection.InsertOne(message);
return true;
}
catch (Exception Ex)
{
Console.WriteLine(Ex.Message);
}
}
return false;
}
Example of read method:
public bool GetFirstAndLastMessageTime(out DateTime firstMessageTime, out DateTime lastMessageTime)
{
if (BsonClassMap.IsClassMapRegistered(typeof(MessageContainer)))
{
BsonClassMap.RegisterClassMap<MessageBase>(cm =>
{
cm.AutoMap();
cm.SetIsRootClass(true);
});
}
var filter = Builders<MessageContainer>.Filter.Empty;
var first = _mongoCollection.Find(filter).Sort(Builders<MessageContainer>.Sort.Ascending("TimeStamp")).Limit(5).ToList().First();
var last = _mongoCollection.Find(filter).Sort(Builders<MessageContainer>.Sort.Descending("TimeStamp")).Limit(5).ToList().First();
firstMessageTime = first.TimeStamp;
lastMessageTime = last.TimeStamp;
return true;
}
What am i missing for it to be able to initialize the abstract class without the need of writing first?
Well, kind of an anti-pattern here (I don't like adding dependencies from base classes to their implementations), but a quick fix would be to add
[BsonKnownTypes(typeof(MyImplementation))]
where MyImplementation is the type that implements your abstract class.
on your MessageBase class. For me this did the trick - I was able to read the data and deserialize just fine. I didn't have to add any class maps either.
While the answer provided amitla is correct, it was not something that worked for me for multiple reasons.
One is I didn't want to change the abstract class and write down all of the implementations as tags.
Another note is that I found out from the MongoDB forums that apparently the reason for it working only after write is an auto mapper and that is why it worked for me after write.
So to make work this is what I did:
public void RegisterClassMapping(List<string> messagesType)
{
// Map base class first
BsonClassMap.RegisterClassMap<MessageBase>(cm =>
{
cm.AutoMap();
cm.SetIsRootClass(true);
});
if (messagesType.Count > 0)
{
foreach (string message in messagesType) // Do look up for each message
{
Type type = GetType("NAMEOFTHENAMESPACE." + message);
if (type == null)
{
type = GetType("NAMEOFTHENAMESPACE." + message);
}
if (type != null && !BsonClassMap.IsClassMapRegistered(type))
{
BsonClassMap.LookupClassMap(type);
}
}
}
}
And this is the generic GetType I used:
private Type GetType(string typeName)
{
var type = Type.GetType(typeName);
if (type != null) return type;
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
type = a.GetType(typeName);
if (type != null)
return type;
}
return null;
}
I just run this once and its working for the rest of session.
I have a series of generic response objects that are being returned with a property that is an abstract class. The NSwag and NJsonSchema generate the schema with an abstract class, which creates problems. The concrete class is easily determined via reflection, however, there does not seem to be a clean way to get NJsonSchema to replace the abstract type with the appropriate concrete one. What is the correct way to do this?
public abstract class AppRequest<TData> {
public Guid RequestId { get; set; }
}
public class AppResponse<TData> {
public TData Data { get; set; }
public AppRequest<TData> OriginalRequest { get; set; }
}
public class User {
....
}
public class UserRequest: AppRequest<User> {
public Guid UserId { get; set; }
}
NSwag generates response object as AppResponseOfUser which is fine, however, it says that the OriginalRequest property is AppRequestOfUser and that it is abstract. I want to create a SchemaProcessor that remaps this AppRequestOfUser to UserRequest. Something like this:
public class MySchemaProcessor
: ISchemaProcessor
{
public async Task ProcessAsync(SchemaProcessorContext context)
{
if (context.Type.IsGenericOf(typeof(AppResponse<>)))
{
var modelType = context.Type.GenericTypeArguments[0];
var abstractRequestType = typeof(AppRequest<>).MakeGenericType(modelType);
var actualRequestType = modelType.Assembly.GetTypes()
.Single(t => t.IsClass && t.BaseType == abstractRequestType);
var requestSchema = await JsonSchema4.FromTypeAsync(actualRequestType);
var originalRequestProperty = context.Schema.Properties["originalRequest"];
originalRequestProperty.IsReadOnly = true;
originalRequestProperty.IsAbstract = false;
// CHANGE PROPERTY TYPE HERE!!!
}
}
}
Unfortunately, NJsonSchema doesn't seem to be very flexible and there is no clear way on how to do this. I do not want to use a discriminator property. I want to remap to the appropriate concrete type.
In case anyone was wondering, here is the final solution:
var classesToMap = typeof(Startup)
.Assembly
.GetTypes()
.Where(t => t.IsClass && t.BaseType.IsGenericOf(typeof(AppRequest<>)));
var settings = new JsonSchemaGeneratorSettings()
{
FlattenInheritanceHierarchy = true,
};
foreach (var type in classesToMap)
{
var actualSchema = JsonSchema4.FromTypeAsync(type,settings).Result;
options.TypeMappers.Add(new ObjectTypeMapper(type.BaseType, actualSchema));
}
I'm building a sort of library to perform text replacement in a document based on some rule. We built a POC and now I'm trying to create a library as generic as possible.
I have just one problem with inheritance:
This is the simplified representation of the classes/interfaces I'm dealing with:
public interface IRule {}
public interface IReplaceRule<T> : IRule
{
T ReplaceValue { get; set; }
}
public class CachedRules<T> where T : IReplaceRule<object>
{
#region Props
public T RuleTemplate { get; set; }
public IDictionary<string, T> RuleList { get; private set; } = null;
#endregion
public void SetRuleList(IDictionary<string, T> ruleList) { ... }
public bool ContainsRuleByKey(string key) { ... }
public bool TryGetRuleValueByKey(string key, out T rule) { ... }
}
public class SingleRowRule : IReplaceRule<string> { ... }
I also have a class which is like a repository of rules, and inside it I can add as many CachedRules as I need:
public class RulesStorage : AbstractRulesStorage
{
private CachedRules<SingleRowRule> singleRowRules;
public RulesStorage() { ... }
// Bunch of methods not useful for this question
// Here I need to return a list of ChachedRule, but just ofr testing I tried to return only one
public CachedRules<IReplaceRule<object>> GetCachedReplaceRules()
{
return singleRowRules;
}
}
Inside this class I need a method to return all the CachedRules declared in the RulesStorage:
Unfortunately the RulesStorage.GetCachedReplaceRules method give me this error:
Cannot implicitly convert type TestLib.Model.CachedRules<TestLib.Rules.SingleRowRule> to TestLib.Model.CachedRules<TestLib.Abstractions.IReplaceRule<object>
I really don't like the fact that I had to put <object> since IReplaceRule requires a generic and also I'm stuck because I don't know how to return this list of CachedRules without getting this compilation error.
Do you have some idea? Do I have to organize the code differently in your opinion?
Hope I've made myself clear and thanks in advance!
Instead of doing IReplaceRule<object> you can do it the way IEnumerable<T> inherits from IEnumerable. With that minor tweak in place, I create an implicit converter to go from T to IReplaceRule and the constraint in place now ensures I can actually do this safely.
I'm assuming you have a reason to have private CachedRules<SingleRowRule> singleRowRules; and can't just using private CachedRules<IReplaceRule> singleRowRules; which would remove the need for this extra conversion hop.
Code:
public interface IReplaceRule : IRule { object ReplaceValue { get; set; } }
public interface IReplaceRule<T> : IReplaceRule { new T ReplaceValue { get; set; } }
public class CachedRules<T> where T : IReplaceRule
{
public IDictionary<string, T> RuleList { get; private set; } = new Dictionary<string, T>();
//The key ingredient for a nice experience instead of just doing this in the method
public static implicit operator CachedRules<IReplaceRule>(CachedRules<T> rules)
=> new CachedRules<IReplaceRule> { RuleList = rules.RuleList.ToDictionary(x => x.Key, x => x.Value as IReplaceRule) };
}
public class SingleRowRule : IReplaceRule<string>
{
public string ReplaceValue { get; set; }
object IReplaceRule.ReplaceValue { get => ReplaceValue; set => ReplaceValue = value as string; }
}
public class RulesStorage
{
private CachedRules<SingleRowRule> singleRowRules = new CachedRules<UserQuery.SingleRowRule>();
//FIXME: just for testing purposes
public RulesStorage() => singleRowRules.RuleList.Add("Hello", new SingleRowRule { ReplaceValue = "World" });
// Here I need to return a list of ChachedRule, but just ofr testing I tried to return only one
public CachedRules<IReplaceRule> GetCachedReplaceRules() => singleRowRules;
}
I am mapping MongoDB documents to C# objects (see this question for some background) and everything works fine, however I'm starting to find some entries that are null. The reason being the XML previously just had <VehicleEntry></VehicleEntry> tags so it was inserted as 'null' into the array in the BsonDocument.
I can understand that this would be expected behavior, but when I map it to my VehicleEntry class that I wrote, it shows up as a null object. In my mapped class I have listed a bunch of BsonDefaultValues and even added a default constructor, but it still appears that if the value is 'null' in the database it will create a 'null' reference object.
How can I set this up to match a null reference to an object with all default values?
If you create your own BsonSerializers and assign it to the VehicleEntry type you'll then be able to say if the value is null then return a default(VehicleEntry)
[TestFixture]
public class StackQuestionTest
{
[Test]
public void GivenABsonDocumentWithANullForAnPossibleEmbeddedDocument_When_ThenAnInstanceIsSetAsTheEmbeddedDocument()
{
BsonSerializer.RegisterSerializationProvider(new VehicleEntryBsonSerializationProvider());
var document = new BsonDocument()
{
{"OtherProperty1", BsonString.Create("12345")},
{"OtherProperty2", BsonString.Create("67890")},
{"VehicleEntry", BsonNull.Value},
};
var rootObject = BsonSerializer.Deserialize<RootObject>(document);
Assert.That(rootObject.OtherProperty1, Is.EqualTo("12345"));
Assert.That(rootObject.OtherProperty2, Is.EqualTo("67890"));
Assert.That(rootObject.VehicleEntry, Is.Not.Null);
Assert.That(rootObject.VehicleEntry.What, Is.EqualTo("Magic"));
}
}
public class VehicleEntrySerializer : BsonClassMapSerializer<VehicleEntry>
{
public override VehicleEntry Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
if (context.Reader.GetCurrentBsonType() == BsonType.Null)
{
context.Reader.ReadNull();
return new VehicleEntry();
}
return base.Deserialize(context, args);
}
public VehicleEntrySerializer(BsonClassMap classMap) : base(classMap)
{
}
}
public class VehicleEntryBsonSerializationProvider : IBsonSerializationProvider
{
public IBsonSerializer GetSerializer(Type type)
{
if (type == typeof(VehicleEntry))
{
BsonClassMap bsonClassMap = BsonClassMap.LookupClassMap(type);
return new VehicleEntrySerializer(bsonClassMap);
}
return null;
}
}
public class RootObject
{
public string OtherProperty1 { get; set; }
public string OtherProperty2 { get; set; }
public VehicleEntry VehicleEntry { get; set; }
}
public class VehicleEntry
{
public string What { get; set; } = "Magic";
}
See http://mongodb.github.io/mongo-csharp-driver/2.0/reference/bson/serialization/
One solution is to change your LINQ to only return values that aren't null in the list:
var results = collection.AsQueryable()
.Where(v => v.ProjectName.Equals("input")
.SelectMan(v => v.VehicleEntries)
.Where(i => i != null)
.ToList();
This does not solve the problem of having the null value there, but it presents it from being returned in any results and avoids NPEs when displaying the data.