I am trying to create a simple WF4 activity that accepts a string that contains a VB.NET expression (from say the database), evaluates that string using the variables available in the current scope of the workflow and returns the result. Unfortunately, with the ways I've tried it, whether it be with a plain on Activity or a full-fledged NativeActivity, I keep hitting a wall.
My first attempt was with a simple Activity, and I was able to make a simple class that evaluates an expression given some object as its input:
public class Eval<T, TResult> : Activity<TResult>
{
[RequiredArgument]
public InArgument<T> Value { get; set; }
public Eval(string predicate)
{
this.Implementation = () => new Assign<TResult>
{
Value = new InArgument<TResult>(new VisualBasicValue<TResult>(predicate)),
To = new ArgumentReference<TResult>("Result")
};
}
public TResult EvalWith(T value)
{
return WorkflowInvoker.Invoke(this, new Dictionary<string, object>{ {"Value", value } });
}
}
This woks nicely, and the following expression evaluates to 7:
new Eval<int, int>("Value + 2").EvalWith(5)
Unfortunately, I can't use it the way I want since the expression string is given as a constructor argument instead of as an InArgument<string>, so it can't be easily incorporated (dragged and dropped) into a workflow. My second attempt was to try and use NativeActivity to get rid of that pesky constructor parameter:
public class NativeEval<T, TResult> : NativeActivity<TResult>
{
[RequiredArgument] public InArgument<string> ExpressionText { get; set; }
[RequiredArgument] public InArgument<T> Value { get; set; }
private Assign Assign { get; set; }
private VisualBasicValue<TResult> Predicate { get; set; }
private Variable<TResult> ResultVar { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
Predicate = new VisualBasicValue<TResult>();
ResultVar = new Variable<TResult>("ResultVar");
Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };
metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);
}
protected override void Execute(NativeActivityContext context)
{
Predicate.ExpressionText = ExpressionText.Get(context);
context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
}
private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
{
Result.Set(context, ResultVar.Get(context));
}
}
I tried running NativeEval with the following:
WorkflowInvoker.Invoke(new NativeEval<int, int>(), new Dictionary<string, object>
{ { "ExpressionText", "Value + 2" }, { "Value", 5 } });
But got the following exception:
Activity '1: NativeEval' cannot access this variable because it is declared at the scope of activity '1: NativeEval'. An activity can only access its own implementation variables.
So I changed metadata.AddVariable(ResultVar); to metadata.AddImplementationVariable(ResultVar); but then I got a different exception:
The following errors were encountered while processing the workflow tree:
'VariableReference': The referenced Variable object (Name = 'ResultVar') is not visible at this scope. There may be another location reference with the same name that is visible at this scope, but it does not reference the same location.
I tried using .ScheduleFunc() as described here to schedule a VisualBasicValue activity, but the result it returned was always null (but oddly enough no exceptions were thrown).
I'm stumped. The metaprogramming model of WF4 seems much more difficult than the metaprogramming model of System.Linq.Expressions, which albeit difficult and often perplexing (like metaprogramming usually is), at least I was able to wrap my head around it. I guess it's because it has the added complexity of needing to represent a persistable, resumable, asynchronous, relocatable program, rather than just a plain old program.
EDIT: Since I don't think the issue I'm experiencing is caused by the fact that I'm trying to evaluate an expression that isn't hardcoded, the following alteration can be made to the NativeActivity that cause it to have a static expression:
Replace
Predicate = new VisualBasicValue<TResult>();
With
Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
And remove the line
Predicate.ExpressionText = ExpressionText.Get(context);
Now even though with those lines the expression is static, I'm still getting the same errors.
EDIT2: This article addressed the exception I was getting. I had to change both variable and child activity to be an "implementation", so this:
metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);
Changed to this:
metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
And caused all the exceptions to go away. Unfortunately, it revealed that the following line does absolutely nothing:
Predicate.ExpressionText = ExpressionText.Get(context);
Changing the ExpressionText property of a VisualBasicValue during runtime has no effect. A quick check with ILSpy reveals why - the expression text is only evaluated and converted to an expression tree when CacheMetadata() is called, at which point the expression is not yet know, which is why I used the parameterless constructor which initialized and crystallized the expression to a no-op. I even tried saving the NativeActivityMetadata object I got in my own CacheMetadata overridden method and then use reflection to force a call to VisualBasicValue's CacheMetadata(), but that just ended up throwing a different cryptic exception ("Ambiguous match found." of type AmbiguousMatchException).
At this point it doesn't seem possible to fully integrate a dynamic expression into a workflow, exposing all the in-scope variables to it. I guess I'll have the method used in my Eval class within the NativeEval class.
I ended up using the following activity. It can't access the workflow's variables, instead it accepts a single argument 'Value' that can be used by the same name inside the dynamic expression. Other than that it works pretty well.
public class Evaluate<TIn, TOut> : NativeActivity<TOut>
{
[RequiredArgument]
public InArgument<string> ExpressionText { get; set; }
[RequiredArgument]
public InArgument<TIn> Value { get; set; }
protected override void Execute(NativeActivityContext context)
{
var result = new ExpressionEvaluator<TIn, TOut>(ExpressionText.Get(context)).EvalWith(Value.Get(context));
Result.Set(context, result);
}
}
public class ExpressionEvaluator<TIn, TOut> : Activity<TOut>
{
[RequiredArgument]
public InArgument<TIn> Value { get; set; }
public ExpressionEvaluator(string predicate)
{
VisualBasic.SetSettingsForImplementation(this, VbSettings);
Implementation = () => new Assign<TOut>
{
Value = new InArgument<TOut>(new VisualBasicValue<TOut>(predicate)),
To = new ArgumentReference<TOut>("Result")
};
}
public TOut EvalWith(TIn value)
{
return WorkflowInvoker.Invoke(this, new Dictionary<string, object> { { "Value", value } });
}
private static readonly VisualBasicSettings VbSettings;
static ExpressionEvaluator()
{
VbSettings = new VisualBasicSettings();
AddImports(typeof(TIn), VbSettings.ImportReferences);
AddImports(typeof(TOut), VbSettings.ImportReferences);
}
private static void AddImports(Type type, ISet<VisualBasicImportReference> imports)
{
if (type.IsPrimitive || type == typeof(void) || type.Namespace == "System")
return;
var wasAdded = imports.Add(new VisualBasicImportReference { Assembly = type.Assembly.GetName().Name, Import = type.Namespace });
if (!wasAdded)
return;
if (type.BaseType != null)
AddImports(type.BaseType, imports);
foreach (var interfaceType in type.GetInterfaces())
AddImports(interfaceType, imports);
foreach (var property in type.GetProperties())
AddImports(property.PropertyType, imports);
foreach (var method in type.GetMethods())
{
AddImports(method.ReturnType, imports);
foreach (var parameter in method.GetParameters())
AddImports(parameter.ParameterType, imports);
if (method.IsGenericMethod)
{
foreach (var genericArgument in method.GetGenericArguments())
AddImports(genericArgument, imports);
}
}
if (type.IsGenericType)
{
foreach (var genericArgument in type.GetGenericArguments())
AddImports(genericArgument, imports);
}
}
}
EDIT: Updated the class to include complete assembly and namespace imports, lest you get the dreaded (and unhelpful) error message:
'Value' is not declared. It may be inaccessible due to its protection level.
Also, moved the ExpressionEvaluator class outside and made it public, so you can used it outside of WF, like so:
new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);
Which will return:
6.28318530717959
I would suggest to use a different framework for this. One good approach is to use nCalc. http://ncalc.codeplex.com/
It can parse any expression and evaluate the result, including static or dynamic parameters and custom functions.
We use it to evaluate different kind of expressions at runtime.
If your 'predicate' is a well-known string and don't need to be an expression evaluated at runtime you surely can do something like this, throwing away the InArgument and avoid the constructor:
public class Eval<T, TResult> : Activity<TResult>
{
public string Expression { get; set; }
[RequiredArgument]
public InArgument<T> Value { get; set; }
protected override Func<Activity> Implementation
{
get
{
if (string.IsNullOrEmpty(Expression))
{
return base.Implementation;
}
return () => new Assign<TResult>
{
Value = new InArgument<TResult>(new VisualBasicValue<TResult>(Expression)),
To = new ArgumentReference<TResult>("Result")
};
}
set
{
throw new NotSupportedException();
}
}
}
and call it this way:
var activity = new Eval<int, int>() { Expression = "Value + 2" };
var inputArgs = new Dictionary<string, object>()
{
{ "Value", 5 }
};
Console.WriteLine("RESULT: " + WorkflowInvoker.Invoke<int>(activity, inputArgs));
EDIT: check that even with Predicate.ExpressionText not commented, it has no effect whatsoever:
public class NativeEval<T, TResult> : NativeActivity<TResult>
{
[RequiredArgument]
public InArgument<string> ExpressionText { get; set; }
[RequiredArgument]
public InArgument<T> Value { get; set; }
private Assign Assign { get; set; }
private VisualBasicValue<TResult> Predicate { get; set; }
private Variable<TResult> ResultVar { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
ResultVar = new Variable<TResult>("ResultVar");
Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };
metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
}
protected override void Execute(NativeActivityContext context)
{
// this line, commented or not, is the same!
Predicate.ExpressionText = ExpressionText.Get(context);
context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
}
private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
{
// the result will always be the ExpressionText.Length
Result.Set(context, ResultVar.Get(context));
}
}
When you get at Execute() method changing the child implementation has no effect. The execution mode is on and the children tree cannot be altered.
Related
I'm trying to plan a publish/subscribe implementation. I've tried several times but I get stuck cause I think either I don't understand the limitations of C# generics or I haven't gotten the correct way to do it.
I would like to keep a list of Events/Delegates that are associated with a type of object. So when the Method gets called it receives an object of correct type.
Furthermore I would like the way to use it to be like this:
Hub.Subscribe<MyMessageType>( MethodThatTakes<MyMessageType>);
Hub.Publish<MyMessageType>( new MyMessageType("Message"));
Hub.Subscribe<Vector3>( MethodThatTakes<Vector3>);
Hub.Publish<Vector3>( new Vector3(45,100,0));
My problem is I don't manage to get it to work this way. It becomes more complex to use but I'm thinking that the user shouldn't need to do more since more information is not needed.
So my question is if it's possible to make it work like this with generics or maybe I misunderstand something about how a pub sub could and should work?
Example of code to register a subscription
public static void Subscribe < T > (string title = "", CallbackMethod<T>) {
Subscriptions subs;
if (subscriptions.Any(sub => sub.Type == typeof (T) && sub.Title == title)) {
subs = subscriptions.First(sub => sub.Type == typeof (T) && sub.Title == title);
} else {
subs = new Subscriptions < T > (title);
}
}
class Subscriptions < T > {
internal Type Type;
List < CallbackMethodsWithParameter < T >> subscribers;
public Subscriptions() {
Type = T.GetType();
}
}
So here I am trying to have a subscriptions class store type of return and a list with methods to callback that takes an object of the same type.
It obviously doesn't work but that's where I am now and I'm not sure if it's even possible or a good way to do it.
Your sample registration code seems overly complex for what you are trying to achieve, which is simply registering an Action<T> and then publishing a message of T to all subscriptions.
However, this is not easily mapped for your use case since Action<in T> is contravariant and you need to store your handlers as Action<object> which would be a covariant interaction. That said, you can wrap the subscription handler and cast the incoming message.
public class Hub
{
private static object _subscriptionLock = new object();
private static ConcurrentDictionary<Type, ConcurrentBag<Action<object>>> _map =
new ConcurrentDictionary<Type, ConcurrentBag<Action<object>>>();
public void Subscribe<T>(Action<T> subscriptionHandler)
{
var entryExists = _map.TryGetValue(typeof(T), out var entry);
var wrappedHandler = Wrap(subscriptionHandler);
if (!entryExists)
{
entry = new ConcurrentBag<Action<object>>();
}
entry.Add(wrappedHandler);
_map[typeof(T)] = entry;
}
public void Publish<T>(T message)
{
var entryExists = _map.TryGetValue(typeof(T), out var entry);
if (!entryExists) return;
foreach (var handler in entry)
{
handler(message);
}
}
private Action<object> Wrap<T>(Action<T> input)
{
Action<object> f = (message) => input((T)message);
return f;
}
}
This allows the type of subscription and publish API that you are looking for:
void Main()
{
var hub = new Hub();
hub.Subscribe<TestMessageA>(HandlerOne);
hub.Subscribe<TestMessageA>(HandlerTwo);
hub.Subscribe<TestMessageB>(HandlerOne);
hub.Subscribe<TestMessageB>(HandlerTwo);
hub.Publish(new TestMessageA { Title = "hey there!" });
hub.Publish(new TestMessageB { Title = "hey there again!" });
}
public void HandlerOne(TestMessageA message)
{
Console.WriteLine($"{message.Title} in HandlerOne");
}
public void HandlerTwo(TestMessageA message)
{
Console.WriteLine($"{message.Title} in HandlerTwo");
}
public void HandlerOne(TestMessageB message)
{
Console.WriteLine($"{message.Title} in HandlerOne");
}
public void HandlerTwo(TestMessageB message)
{
Console.WriteLine($"{message.Title} in HandlerTwo");
}
public class TestMessageA
{
public int Id { get; set; }
public string Title { get; set; }
}
public class TestMessageB
{
public int Id { get; set; }
public string Title { get; set; }
}
which outputs:
hey there! in HandlerOne
hey there! in HandlerTwo
hey there again! in HandlerOne
hey there again! in HandlerTwo
Setup:
public class Data
{
public int A { get; set; }
public int B { get; set; }
}
public class Runner
{
public static void Run(Data data)
{
data.A = data.B;
data.A = 1;
}
}
class Program
{
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
Runner.Run(data);
}
}
Problem: I need to implement change tracking here for property names not values. Inside Runner.Run on the first line data.A = data.B I need to record somehow that "A" was set to "B" (literally property names) and then on the next line data.A = 1 I need to record that "A" was set to constant and say forget about it.
Constrains:
When setting one property to another (e.g. A = B) that needs to be recorded
When setting property to anything else (e.g. A = 1 or A = B * 2) this change needs to be forgotten (e.g. remember A only)
Suppose this is the tracker contract being used:
void RecordChange(string setterName, string getterName);
void UnTrackChange(string setterName);
Question:
I would like to somehow proxy the Data class so it still can be used in the interface code (e.g. Runner - is a whole bunch of a business logic code that uses Data) INCLUDING strong-typing and it can track it's changes without modifying the code (e.g. there is lots of places like 'data.A = data.B').
Is there any way to do it without resorting to I guess some magic involving IL generation?
Already investigated/tried:
PostSharp interceptors/Castle.DynamicProxy with interceptors - these alone cannot help. The most I can get out of it is to have a value of data.B inside setter interceptor but not nameof(data.B).
Compiler services - haven't found anything suitable here - getting the name of caller doesn't really help.
Runtine code generation - smth like proxy inherited from DynamicObject or using Relfection.Emit (TypeBuilder probably) - I lose typings.
Current solution:
Use the Tracker implementation of the abovementioned contract and pass it around into every function down the road. Then instead of writing data.A = data.B use method tracker.SetFrom(x => x.A, x => x.B) - tracker holds a Data instance and so this works. BUT in a real codebase it is easy to miss something and it just makes it way less readable.
It is the closest the solution I've come up with. It isn't perfect as I still need to modify all the contracts/methods in the client code to use a new data model but at least all the logic stays the same.
So I'm open for other answers.
Here's the renewed Data model:
public readonly struct NamedProperty<TValue>
{
public NamedProperty(string name, TValue value)
{
Name = name;
Value = value;
}
public string Name { get; }
public TValue Value { get; }
public static implicit operator TValue (NamedProperty<TValue> obj)
=> obj.Value;
public static implicit operator NamedProperty<TValue>(TValue value)
=> new NamedProperty<TValue>(null, value);
}
public interface ISelfTracker<T>
where T : class, ISelfTracker<T>
{
Tracker<T> Tracker { get; set; }
}
public class NamedData : ISelfTracker<NamedData>
{
public virtual NamedProperty<int> A { get; set; }
public virtual NamedProperty<int> B { get; set; }
public Tracker<NamedData> Tracker { get; set; }
}
Basically I've copy-pasted the original Data model but changed all its properties to be aware of their names.
Then the tracker itself:
public class Tracker<T>
where T : class, ISelfTracker<T>
{
public T Instance { get; }
public T Proxy { get; }
public Tracker(T instance)
{
Instance = instance;
Proxy = new ProxyGenerator().CreateClassProxyWithTarget<T>(Instance, new TrackingNamedProxyInterceptor<T>(this));
Proxy.Tracker = this;
}
public void RecordChange(string setterName, string getterName)
{
}
public void UnTrackChange(string setterName)
{
}
}
The interceptor for Castle.DynamicProxy:
public class TrackingNamedProxyInterceptor<T> : IInterceptor
where T : class, ISelfTracker<T>
{
private const string SetterPrefix = "set_";
private const string GetterPrefix = "get_";
private readonly Tracker<T> _tracker;
public TrackingNamedProxyInterceptor(Tracker<T> proxy)
{
_tracker = proxy;
}
public void Intercept(IInvocation invocation)
{
if (IsSetMethod(invocation.Method))
{
string propertyName = GetPropertyName(invocation.Method);
dynamic value = invocation.Arguments[0];
var propertyType = value.GetType();
if (IsOfGenericType(propertyType, typeof(NamedProperty<>)))
{
if (value.Name == null)
{
_tracker.UnTrackChange(propertyName);
}
else
{
_tracker.RecordChange(propertyName, value.Name);
}
var args = new[] { propertyName, value.Value };
invocation.Arguments[0] = Activator.CreateInstance(propertyType, args);
}
}
invocation.Proceed();
}
private string GetPropertyName(MethodInfo method)
=> method.Name.Replace(SetterPrefix, string.Empty).Replace(GetterPrefix, string.Empty);
private bool IsSetMethod(MethodInfo method)
=> method.IsSpecialName && method.Name.StartsWith(SetterPrefix);
private bool IsOfGenericType(Type type, Type openGenericType)
=> type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType;
}
And the modified entry point:
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
NamedData namedData = Map(data);
var proxy = new Tracker<NamedData>(namedData).Proxy;
Runner.Run(proxy);
Console.ReadLine();
}
The Map() function actually maps Data to NamedData filling in property names.
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
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.
I have this event class:
sealed class AnEvent : EventArgs
{
IEnumerable<ItemWrapper<AnAbstractClass>> Items { get; set; }
}
Which is used like this:
class ItemsProcessor
{
delegate void OnItemsProcessedHandler(object sender, AnEvent e);
event OnItemsProcessedHandler OnItemsProcessed;
//...
}
I use this wrapper class:
sealed class ItemWrapper<T>
where T: AnAbstractClass
{
T Item { get; set; }
Metadata Metadata { get; set; }
ItemWrapper(T item, Metadata metadata)
{
Item = item;
Metadata = metadata;
}
}
And I have this method in ItemsProcessor class :
internal void DoSomethingWithList<T>(IEnumerable<T> items)
where T: AnAbstractClass, new()
{
IEnumerable<ItemWrapper<T>> processedItems = WrapItems<T>(items);
OnItemsProcessed(this, new AnEvent() { Items = processedItems }); //error here
}
The issue is on the last line of this code sample; when I try to set the property Items of AnEvent with my local IEnumerable. The compiler refuses to proceed telling me that it cannot implicitly cast an IEnumerable<ItemWrapper<T>> to an IEnumerable<ItemWrapper<AnAbstractClass>>. I thought it should be okay since I added the constraint where T: AnAbstractClass, new() for this method but even when explicitly casting (using either classic casting with parenthesis or using Convert<>) I get an InvalidCastException.
My current workaround for the method DoSomethingWithList is :
var temp = processedItems.Select(x =>
{
return new ItemWrapper<AnAbstractClass>(x.Item, x.Metadata);
});
OnItemsProcessed(this, new AnEvent() { Items = temp });
So it is working fine now but I was wondering why couldn't it work without using this LINQ conversion which has to iterate over all the items in the list? It seems obvious to me that you should be able to cast it without any error since I added the constraint and even with an explicit cast to make the compiler accepts my code there is an exception raised... Anyone could point me what is going wrong here?
abstract class AnAbstractClass
{
}
class ItemClass : AnAbstractClass
{
}
A WrapItems quick implementation for easy copy-pasting if you want to try:
IEnumerable<ItemWrapper<T>> WrapItems<T>(IEnumerable<T> items)
where T : AnAbstractClass, new()
{
List<ItemWrapper<T>> ret = new List<ItemWrapper<T>>();
foreach (var item in items)
{
ret.Add(new ItemWrapper<T>(item, new Metadata()));
}
return ret;
}
If you change ItemWrapper to
sealed class ItemWrapper
{
AnAbstractClass Item { get; set; }
Metadata Metadata { get; set; }
ItemWrapper(AnAbstractClass item, Metadata metadata)
{
Item = item;
Metadata = metadata;
}
}
what have you lost?