Background: Hello I am trying to build a windows workflow like state engine. I have a basic engine set up with Action‘s and Trigger‘s – Actions do custom code, triggers are external events that allow the state engine to move from one state to another. Trigger‘s hold many Actions‘ which fire when a Trigger‘s bool isMet() condition is true.
The coding problem that I am having is I need to abstract the isMet() method of the Trigger class. The reason for this is that I have many sub-Trigger classes e.g. isPaperworkCompletedTrigger that inherit from the base Trigger class and they each contain their own custom isMet() code. The only complication I’m having in implementing this is that the whole engine e.g. Trigger‘s and Action‘s need to be stored in a database. I built the engine tables in SQL first, then used LINQ-to-SQL to build my Action and Trigger objects. LINQ-to-SQL does allow you to expand on auto generated class objects by using the partial class method which I have used to add a isMet() method to my Trigger class, I cannot make this isMet() method abstract because the auto-generated Trigger class is not abstract ( for obvious reasons ).
I have tried ‘soft overriding’ the isMet() method by inheriting the base Trigger class in my sub-classes e.g. isPaperworkCompletedTrigger and creating a method called isMet(), intellisense complains about this a little bit and tells me to stop intellisense from complaining to use the ‘new’ keyword on the method. As expected this method of ‘soft-overriding’ does not work.
When the Triggerobjects are pulled out of the database and the isMet() method is called naturally the base method isMet() method is called ( from the Trigger class, and not the sub-class ), this makes sense as the database has no way of knowing which child of Trigger to call the isMet() method on.
The obvious solution to this is to stick a TriggerName field in the Triggers table, and do a good old switch case on this field, calling the isMet() method of the corresponding sub-class of Trigger based on what the name field is. This is something I want to avoid.
I would like this project to be able to allow users to ‘plug-in’ Trigger‘s and Action‘s. The way I plan to accomplish this is to allow user’s to drop their own custom Trigger derived classes as a DLL into a specified folder, and have the workflow engine able to use these without a re-deploy or rebuild ( which rules out the massive switch case statements on static strings ).
The core of this problem is working out how to read in all the Trigger modules ( one DLL is one Trigger module ), and call a isMet() method on this object ( without having access to its class code ).
I suspect that the point-of-attack to solve this lies within making the Trigger class isMet() method abstract OR Putting some kind of converter class to convert from the database Trigger class to an ‘offline’ Trigger class and making that offline class abstract ( which I can override from ).
Can anybody help with this problem.
Very sorry for my novel-lengthed question but the issue does require quite a lot of information in order for anybody to understand the question.
Thanks
Instead of making the
isMet()method on the baseTriggerclassabstract, make itvirtualwith perhaps the default value being false. Then you canoverridethe method in the derived classes using theoverridekeyword.Your second problem concerns serializing and deserializing the triggers to a database. When you deserialize, you want to make sure you’re getting back the derived trigger type, not the base. I don’t know how you’ve chosen to serialize your objects to the databse, but you’ll need a way to store the type. Lets take the
DataContractSerializerfor example. It takes in aTypeas it’s first parameter. If you store the typeof(DerivedTrigger) to another field in your database when you serialize your triggers, you can deserialize the Type and use that to deserialize the Trigger to the correct derived type. Then calling yourisMet()method should call the derived overriden value. Here is a short example using static variables in place of a database:EDIT
Ok, using the MemoryStream seems to have confused the situation. The MemoryStream isn’t what gets stored in the database, it IS the database.
The whole reason for having the
serializedObjectTypeis because, like you say, usingtypeof(Trigger)for the type in a DataContractSerializer won’t deserialize objects that are actually derived triggers. Therefore, you need to store the derived type along with the object in the database.You haven’t said what dbms you’re using but I would use a blob to represent the Trigger column, and either a varbinary or a blob to represent the
serializedObjectTypecolumn, ie the actual most derived type of the Trigger. Serialize the type with a hardcoded type serializer. ieDataContractSerializer(typeof(Type), ...)and serialize the object with aDataContractSerializer(typeof(T), ...)where T is the derived trigger type which you can get with the generic type variable.When you deserialize, do it in reverse. First deserialize the type using a hardcoded type serializer. ie
DataContractSerializer(typeof(Type), ...)and then deserialize the object with the results of the deserialized type. I’ve updated my code snippets to hopefully better illustrate my proposed strategy. Sorry for the lateness in my response.EDIT 2
Typically, when talking about serializing, you serialize only the values in an object, since that is what separates one object from another. You don’t need to serialize the method body to the database because that is stored in the assemblies in the file system (the plug-able dlls you mention in your question). The loading of these dlls is a separate step all-together. look into
System.Reflection.Assembly.LoadFile().From these two statements in your question:
and
I assumed (perhaps incorrectly) that the definition for the class would be stored on the fs and the data that goes into each class’s objects would be stored in the database. If your derived
isMet()methods are static (maybe not explicitly, but doesn’t have any associated state), then there isn’t going to be anything to store in the database. However, it sounds like you’re setting it up so that aTriggerholds a collection ofActions. In that case, thoseActionsare what gets serialized to the database. Just mark them public if your class isSerializableor else mark the collection directly asSerializable. The size of the memory stream, then, is going to be proportional to the number ofActionseach trigger holds. Clear as mud?