Mapping dynamic-component by code with variable number of properties

213 views
Skip to first unread message

Exodus

unread,
Mar 14, 2012, 5:04:55 AM3/14/12
to nhu...@googlegroups.com
Hi,

I have this code:

mapper.Class<Test>(ca =>
                        {
                            ca.Component(x => x.DynamicAttributes, new
                                {
                                    SomeStringAttribute1 = ""
                                }, dc =>
                                        {
                                            dc.Property(arg => arg.SomeStringAttribute1);
                                        });
                        }
    );

Is there a way to map a variable number of "SomeStringAttributes" where I know the number first at runtime. I know I could generate XML for the mapping and do it that way, but is it a way to do this when mapping by code? :)

Regards,
Peter

Adam Bar

unread,
Mar 14, 2012, 12:55:04 PM3/14/12
to nhusers
DynamicComponent is generally for the cases when you know the
attributes upfront and the only "dynamic" part is that you don't have
it as a properties in your object model, but in a IDictionary instead.

If you're looking for truly dynamic key-value store, you probably want
to use Map:
http://ayende.com/blog/4045/nhibernate-mapping-map

and mapping-by-code:
http://notherdev.blogspot.com/2012/02/mapping-by-code-map.html

Regards,
Adam

Gunnar Liljas

unread,
Mar 14, 2012, 3:46:39 PM3/14/12
to nhu...@googlegroups.com
If the dynamic setup changes very rarely, dynamic-component works great. Create a new configuration and a new session factory, update the schema and replace the current session factory. Of course a bit of careful coding is recommended, but the end result can be really nice and efficient. In many ways superior to a standard EAV model.

I don't think it could be accomplished using ByCode mapping (yet), since Property() hard wires things using lambdas, but I guess it would work to manipulate the actual mapping meta data, just before the configuration is created.

/G


2012/3/14 Adam Bar <adam...@gmail.com>
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.


Exodus

unread,
Mar 15, 2012, 5:44:57 AM3/15/12
to nhu...@googlegroups.com
Adam, great blog series you have on mapping by code btw. I do know the attributes upfront, it's just that I don't know them at compile time, I know them only after I've read my config-file. Correct me if I'm wrong, but I think the map is more for the general case where you have an EAV-type of database schema. For performance reasons, I'd like the attributes I add to be stored in regular columns.

Gunnar, Yea I've investigated a bit but I haven't found a way to do it in ByCode either. I also tried manipulating the mapping object hierarchy afterwards but I was not sure how to do that, it didn't look like HbmDynamicComponent had any methods to add new properties. But I am probably looking in the wrong places :)

//P

barry schulz

unread,
Apr 2, 2012, 6:15:18 PM4/2/12
to nhu...@googlegroups.com
I was able to add a dynamic-component and populate that component with the correct properties at runtime by using the following link as a starting point.


You can still use the by code configuration to setup your dynamic component. Notice the anonymous type has no properties (because they are not known). 

    mapper.Class<Test>(map =>
         {
            map.Component("DynamicAttributes"new { },componentMap =>
            {
               componentMap.Access(Accessor.NoSetter); // I do not have a setter
            });
            
         });

After you add your mappings to the Configuration, add the code seen in AddMappingExtensions (from the link). You'll have to modify it based on how you get your dynamic attribute metadata. 

Example:

         foreach (var classMapping in config.ClassMappings)
         {
            // only do this for the class named Test for testing...
            if (classMapping.EntityName != "YourCoolNamepsace.Test")
            {
               continue;
            }
            var componentProperty = classMapping.GetProperty("DynamicAttributes");
            var component = (Component)componentProperty.Value;
 
            //foreach (TODO: get user defined attributes from somewhere)
            {               
               // CreateDynamicProperty is hard coded to work with string for testing...
               component.AddProperty(CreateDynamicProperty("SomeDynamicAttribute", classMapping));
               component.AddProperty(CreateDynamicProperty("SomeOtherDynamicAttribute", classMapping));             }          }


Fabio Maulo

unread,
Apr 7, 2012, 5:35:05 PM4/7/12
to nhu...@googlegroups.com
What you mean with "variable number" ?
"dynamic" is on object side, in the DB you have just Tables and Columns.

I'm not sure about what you are trying to do but perhaps an Expando may help you

Exodus

unread,
Apr 10, 2012, 5:09:32 AM4/10/12
to nhu...@googlegroups.com
Fabio: Basicly I want to change the mapping of a dynamic component because the database schema has changed, and do it at runtime. So I want to add properties to the dynamic component in a for-loop.

Barry: Good find, didn't find that link so I implemented my own version, it uses a template which I map by code and then I copy the template to my variable number of attributes. This code works in my limited case, but is not very general I suspect. Code is below.

modelMapper.Class<TestClass>(c =>
                              {
                                c.Table("TestClass");
                                ...
                                c.Component(x => x.Properties,
                                          new
                                              {
                                                  TEMPLATE = ""
                                              }
                                          , dc =>
                                                {
                                                    dc.Property(arg => arg.TEMPLATE);
                                                });
                               });

...
HbmMapping mapping = modelMapper.CompileMappingForAllExplicitlyAddedEntities();

var dynamicComponentMappingChanger = new NHibernateDynamicComponentMappingChanger();
dynamicComponentMappingChanger.ClassName = "TestClass";
dynamicComponentMappingChanger.PropertyName = "Properties";
dynamicComponentMappingChanger.AddMappingChange("MyAttribute1", "TEMPLATE");
dynamicComponentMappingChanger.AddMappingChange("MyAttribute2", "TEMPLATE");
dynamicComponentMappingChanger.AddMappingChange("MyAttribute3", "TEMPLATE");
dynamicComponentMappingChanger.ChangeMapping(mapping);

    public class NHibernateDynamicComponentMappingChanger
    {
        public string ClassName { get; set; }
        public string PropertyName { get; set; }
        private Dictionary<string, List<string>> _mappingToChange = new Dictionary<string, List<string>>();

        /// <summary>
        /// First string is new property name, second string is template that is already mapped
        /// </summary>
        public void AddMappingChange(string newPropertyName, string nameOfPropertyToUseAsTemplate)
        {
            _mappingToChange.Add(nameOfPropertyToUseAsTemplate, newPropertyName);
        }
           
        public void ChangeMapping(HbmMapping mapping)
        {
            HbmClass hbmClass = mapping.RootClasses.Single(x => x.Name == ClassName);
            HbmDynamicComponent dynamicComponent = (HbmDynamicComponent) hbmClass.Properties.Single(x => x.Name == PropertyName);

            object[] items = dynamicComponent.Items;
            List<object> result = new List<object>();

            foreach(Object o in items)
            {
                HbmProperty hbmProperty = o as HbmProperty;
                if(hbmProperty != null)
                {
                    List<string> list;
                    if(_mappingToChange.TryGetValue(hbmProperty.name, out list))
                    {
                        foreach(string s in list)
                        {
                            HbmProperty clone = ObjectCopier.Clone(hbmProperty);
                            clone.name = s;
                            result.Add(clone);
                        }
                    }
                    else
                    {
                        result.Add(hbmProperty);
                    }
                } else
                {
                    result.Add(o);
                }
            }

            dynamicComponent.Items = result.ToArray();
        }
    }


    /// <summary>
    /// Extension functions that makes it possible to have multiple values for a key in a Dictionary. Dictionary value type should be of type List<>
    /// </summary>
    public static class MultimapExt
    {
        public static void Add<TKey, TValue, TCollection>(
            this IDictionary<TKey, TCollection> dictionary,
            TKey key,
            TValue value
        ) where TCollection : ICollection<TValue>, new()
    {
        TCollection collection;
        if(!dictionary.TryGetValue(key, out collection))
        {
            collection = new TCollection();
            dictionary.Add(key, collection);
        }

        collection.Add(value);
    }

        public static bool Remove<TKey, TValue, TCollection>(
            this IDictionary<TKey, TCollection> dictionary,
            TKey key,
            TValue value
        ) where TCollection : ICollection<TValue>
        {
            TCollection collection;
            if(dictionary.TryGetValue(key, out collection))
            {
                bool removed = collection.Remove(value);

                if(collection.Count == 0)
                    dictionary.Remove(key);

                return removed;
            }

            return false;
        }
    }

    public static class ObjectCopier
    {
        /// <summary>
        /// Perform a deep Copy of the object.
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static T Clone<T>(T source)
        {
            if(!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }

            // Don't serialize a null object, simply return the default for that object
            if(Object.ReferenceEquals(source, null))
            {
                return default(T);
            }

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using(stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
Reply all
Reply to author
Forward
0 new messages