Conventionally adding components dynamically
In our application, we want to give end users the ability to customize the application. In the general case, this will involve users running a sql script to modify the schema and then they'll modify an assembly adding fields to an existing entity. Here's an example from our application:
public class SiteExtensions : Extends<Site>
{
public string ExtraString { get; set; }
public int ExtraNumber { get; set; }
public DateTime? ExtraDate { get; set; }
public string Year { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
The script the user executes will look something like this:
BEGIN TRANSACTION
GO
ALTER TABLE dbo.Site ADD
x_ExtraString nvarchar(50) NULL,
x_ExtraNumber int NULL,
x_ExtraDate datetime NULL,
x_Year nvarchar(20) NULL,
x_Make nvarchar(50) NULL,
x_Model nvarchar(100) NULL
COMMIT
Right now I have this working with the following:
In our base class for domain mappings, we go off and look to see if the entity we're on has any extensions. If it does, we create a component part and add it. Here's the code that does it:
protected DomainMap()
{
// For every DomainEntity class, use the Id property
// as the Primary Key / Object Identifier
Id(x => x.Id).Column("id").GeneratedBy.GuidComb();
Table(typeof(T).Name);
Map(x => x.LastModified);
Map(x => x.Created);
if (ExtensionProperties.HasExtensionFor(typeof(T)))
{
var componentType = typeof (ExtensionComponent<>)
.MakeGenericType(ExtensionProperties.ExtensionFor(typeof (T)));
if (componentType != null)
{
var component = Activator.CreateInstance(componentType) as IComponentMappingProvider;
(Components as IList<IComponentMappingProvider>).Add(component);
}
}
}
This constructor gets called when we do the activator.create instance above.
public class ExtensionComponent<T> : ComponentPart<T>
{
public ExtensionComponent()
: base(typeof(T), ReflectionHelper.GetProperty<DomainEntity>(x => x.ExtendedProperties).ToMember())
{
//Assume that all properties are persistable
typeof (T).GetProperties().Where(x => x.DeclaringType == typeof (T)).Each(prop =>
{
if (prop.PropertyType == typeof (DateTime))
{
throw new InvalidExtensionPropertyType(
"Extension properties that hold DateTime values must be nullable. Change {0}.{1} to Nullable<DateTime>.".ToFormat(
typeof (T).Name, prop.Name));
}
//See the "x_" prefix for the naming convention?
Map(prop.ToMember(), "x_" + prop.Name);
});
}
}
This is fine, and it works. However, I'd like this to be convention based so I can get this out of my domain map logic. I guess my real question is what convention interface I need to implement. I looked at IClassConvention, but the problem is I can't get my component added because the Components property on IClassInstance is an enumerable of IComponentBaseInspector and I need to add an instance of IComponentMappingProvider.
Hopefully I'm missing something obvious here, if someone could just point me in the right direction I would be very appreciative.
Thanks in advance --
Brandon
Comments are currently closed for this discussion. You can start a new one.
Support Staff 2 Posted by James Gregory on 12 Mar, 2010 09:39 AM
I don't think you have any other options than what you're already doing. You're already being naughty by upcasting one of our properties.
Conventions are mainly for modifying what has already been created. Specifically, you've already defined the "shape" of your mappings, and conventions just alter things like cascades, column names etc...
What you really need to be able to do is hook into the visitor pipeline when we generate our mappings; these raw mappings can be extended as much as you like. However, I don't think there's an easy way to do that currently. It's planned for the future, but right now I think you're out of luck.
Everything takes place in the PersistenceModel, and you can see in the constructor that it builds up a
visitors
collection; it's this you'd need to add to, but we don't expose a way to do it yet.3 Posted by brandonbehrens on 12 Mar, 2010 05:40 PM
Thanks for the reply James. Obviously upcasting the property is undesireable. The other option (I could see) to do what I wanted was to hit the backing field directly (which does not require a cast) but I didn't like that either. The real issue here is we want to give end users the ability to customize the database (so they can add fields that will flow on to a form) and that doesn't flow from the schema generation.
The user just drops an assembly in a directory and we need to pick up the user modifications and then have the persistence mapped when we save the item.
I'm glad to hear this is planned for the future, as I don't really think it's THAT odd. As long I'm hearing "that's hacky, but it's the only way to do it right now" this will have to do.
Thanks again for getting back to me.
--Brandon
Support Staff 4 Posted by James Gregory on 17 Mar, 2010 09:53 AM
No worries. I'm glad you've got a work around; and don't worry, it's not that strange, we have had people make similar requests in the past.
The changes I've got planned won't make it into the pending 1.1 release, but we won't making any changes that'll break your current implementation either.
James Gregory resolved this discussion on 17 Mar, 2010 09:53 AM.