Add property to POCO class at runtime
Asked Answered
I

4

8

I selected ServiceStack OrmLite for my project which is a pure Data-Oriented application. I am willing to allow the end user to create his own Object Types defined in an XML format that will be used to generate classes at runtime using CodeDOM.

I will be also defining some "system" objects required by the application (i.e. User) but I cannot foresee all the properties the end user will use and therefore I am looking for a way to allow extending the classes I create in design time. Sample bellow

public class User
{
    public Guid Uid { get; set; }
    public String Username { get; set; }
    public String Password { get; set; }
}

The end user wants to have an Email and an Address. He should be able to add the 2 properties to the upper class and the whole class will be (which still can be used by OrmLite, since it allows overwriting :

public class User
{
    public Guid Uid { get; set; }
    public String Username { get; set; }
    public String Password { get; set; }
    public String Email{ get; set; }
    public String Address { get; set; }
}

I know that there might be a risk of doing so to crash the system (if the class is already instantiated) so I am looking for the best way to avoid this issue and mimic the need I have.

Inlaw answered 20/1, 2013 at 0:23 Comment(0)
B
4

It seems that there are two parts to what you're doing here. You need to create types dynamically to support the additional properties. You also need to ensure that you never end up with duplicate types in your AppDomain, i.e. two different definitions of User.

Runtime type generation

The various suggestions already given handle how to create the types. In one project, we had something similar. We created a base class that had the core properties and a dictionary to store the 'extension' properties. Then we used Reflection.Emit to create a derived type that had the desired properties. Each property definition simply read from or wrote to the dictionary in the base class. Since Reflection.Emit entails writing low-level IL code, it seems complex at first. We wrote some sample derived classes in another class library and compiled them. These were examples of what we'd actually need to achieve at runtime. Then we used ildasm.exe to see what code the compiler produced. This made it quite easy to work out how we could generate the same code at runtime.

Avoiding namespace collisions

Your second challenge is to avoid having duplicate type names. We appended a guid (with invalid characters removed) to the name of each generated type to make sure this never happened. Easy fix, though I don't know whether you could get away with that with your ORM.

If this is server code, you also need to consider the fact that assemblies are never unloaded in .NET. So if you're repeatedly generating new types at runtime, your process will continue to grow. The same will happen in client code, but this may be less of an issue if you don't expect the process to run for an extended period of time.

I said assemblies are not unloaded; however, you can unload an entire AppDomain. So if this is server code you could have the entire operation run in its own appdomain, then tear it down afterwards to ensure that the dynamically created types are unloaded.

Bereniceberenson answered 28/1, 2013 at 17:4 Comment(2)
This is actually very close to the solution I came up with. I created a Property and DataObjectType classes with an N<->N relationship. I then used the CodeDOM code generator to build a new ExtensionAssembly which will create new classes using the extension properties from the tables above. I attached an ExtensionKey as guid for each of the original Types that is used by the corresponding new types as identifier.Inlaw
Escaping the naming conflict is as easy as adding a prefix or a suffix to the newly created types i.e. User and UserExtensionInlaw
R
1

Check out the ExpandoObject, which provides dynamic language support for doing something like this. You can use it to add additional properties to your POCO's at runtime. Here's a link on using .NET's DLR features: http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject%28v=vs.100%29.aspx

Rhodarhodamine answered 27/1, 2013 at 0:8 Comment(1)
You can only add properties to an instance of the Expando object. I need to add properties to the type.Inlaw
E
0

Why not use a key value pair for all its properties, or at least the dynamic ones?

http://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx

You can do it the way you're describing with Reflection but it will take a performance hit, this way will allow removal of properties also.

Eleonoraeleonore answered 20/1, 2013 at 0:28 Comment(2)
because OrmLite needs them as properties as far as I know. The Htable will be serialized into textInlaw
This may help.. msdn.microsoft.com/en-us/library/ms404245.aspx You could make a base class and then use this to create the dynamic class at runtime. Or you cold use Reflection.EmitEleonoraeleonore
D
0

The project I'm currently working on has a similar requirement. We have a system already in production and had a client request addition fields.

We solved this by simply adding a CustomFields property to our model.

public class Model: IHasId<Guid>
{
    [PrimaryKey]
    [Index(Unique = true)]
    public Guid Id { get; set; }

    // Other Fields...

    /// <summary>  
    /// A store of extra fields not required by the data model.  
    /// </summary>  
    public Dictionary<string, object> CustomFields { get; set; }  
}

We've been using this for a few weeks with no issues.

An additional benefit we found from this was that each row could have its own custom fields so we could handle them on a per record basis instead of requiring them for every record.

Deposal answered 23/1, 2013 at 18:6 Comment(2)
What if you have too much fields and value that the blobbed value will exceed the default 8000 character limit?Inlaw
Valid point, we are actually constraining the more complex data into a separate table that is referenced from an id value in the CustomField's list. We don't anticipate our clients requiring a large number of custom fields but you are correct, there is the limit on the blobbed field to take into consideration.Deposal

© 2022 - 2024 — McMap. All rights reserved.