-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 
Author Message
 Post subject: Domain model spanning multiple assemblies
PostPosted: Wed Apr 25, 2007 4:32 am 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

After refactoring my domain model three days ago (splitting my mapping files across two assemblies), I stumbled across the strange limitation of only supplying one assembly to NHibernate for generating the mappings.

After searching the forum I found this thread.

As I understood it, Sergey said that NHibnernate will support multiple assemblies in the future. Since I do not have time to wait for the future, I simply implemented this myself, but only for the HbmSerializer class (since this is the class I'm using as I like to use attributes). Anyway, if other need this functionality, here is the method I've added to the HbmSerializer class. It is a raw copy of the
Code:
Serialize(System.IO.Stream stream, System.Reflection.Assembly assembly)
method, where I've only changed the needed variables to make this work. A refactoring, merging these two methods, is needed to remove duplicated code. Anyway, this gives a clear idea of what could be done:

Code:
/// <summary> Writes the mapping of all mapped classes of the specified assembly in the specified stream. </summary>
      /// <param name="stream">Where the xml is written.</param>
      /// <param name="assemblies">Assembly used to extract user-defined types containing a valid attribute (can be [Class], [Subclass] or [JoinedSubclass]).</param>
      public virtual void Serialize(System.IO.Stream stream, System.Reflection.Assembly[] assemblies)
      {
         if(stream == null)
            throw new System.ArgumentNullException("stream");
         if(assemblies == null || assemblies.Length == 0)
            throw new System.ArgumentNullException("assemblies");

         System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter( stream, System.Text.Encoding.UTF8 );
         writer.Formatting = System.Xml.Formatting.Indented;
         writer.WriteStartDocument();
         if(WriteDateComment)
            writer.WriteComment( string.Format( "Generated from NHibernate.Mapping.Attributes on {0}.", System.DateTime.Now.ToString("u") ) );
         WriteHibernateMapping(writer, null);

         // Write classes, subclasses and joined-subclasses (classes must come first if inherited by "external" subclasses)
         System.Collections.ArrayList mappedClassesNames = new System.Collections.ArrayList();



                        // Here starts the modification:
         System.Collections.ArrayList domainTypes = new System.Collections.ArrayList();
         foreach (Assembly assembly in assemblies)
         {
            domainTypes.AddRange(assembly.GetTypes());
         }
                        // It more or less stops here, I only have to change the
                        // various collections the foreach loops are using to
                        // extract the types.



         foreach (Type type in domainTypes)
         {
            if( ! type.IsDefined( typeof(ClassAttribute), false ) )
               continue;
            HbmWriter.WriteClass(writer, type);
            mappedClassesNames.Add(type.FullName + ", " + type.Assembly.GetName().Name);
         }

         System.Collections.ArrayList subclasses = new System.Collections.ArrayList();
         System.Collections.Specialized.StringCollection extendedClassesNames = new System.Collections.Specialized.StringCollection();
         foreach (System.Type type in domainTypes)
         {
            if( ! type.IsDefined( typeof(SubclassAttribute), false ) )
               continue;
            bool map = true;
            System.Type t = type;
            while( (t=t.DeclaringType) != null )
               if( t.IsDefined( typeof(ClassAttribute), false )
                  || t.IsDefined( typeof(SubclassAttribute), false ) )
               {
                  map = false; // The class's mapping is included in the mapping of the declaring class
                  break;
               }
            if(map)
            {
               SubclassAttribute attribute = type.GetCustomAttributes(typeof(SubclassAttribute), false)[0] as SubclassAttribute;
               subclasses.Add(type);
               extendedClassesNames.Add(attribute.Extends);
            }
         }
         MapSubclasses(true, subclasses, extendedClassesNames, mappedClassesNames, writer);

         foreach (System.Type type in domainTypes)
         {
            if( ! type.IsDefined( typeof(JoinedSubclassAttribute), false ) )
               continue;
            bool map = true;
            System.Type t = type;
            while( (t=t.DeclaringType) != null )
               if( t.IsDefined( typeof(ClassAttribute), false )
                  || t.IsDefined( typeof(JoinedSubclassAttribute), false ) )
               {
                  map = false; // The class's mapping is included in the mapping of the declaring class
                  break;
               }
            if(map)
            {
               JoinedSubclassAttribute attribute = type.GetCustomAttributes(typeof(JoinedSubclassAttribute), false)[0] as JoinedSubclassAttribute;
               subclasses.Add(type);
               extendedClassesNames.Add(attribute.Extends);
            }
         }
         MapSubclasses(false, subclasses, extendedClassesNames, mappedClassesNames, writer);

         writer.WriteEndElement(); // </hibernate-mapping>
         writer.WriteEndDocument();
         writer.Flush();

         if( ! Validate )
            return;

         // Validate the generated XML stream
         try
         {
            writer.BaseStream.Position = 0;
            System.Xml.XmlTextReader tr = new System.Xml.XmlTextReader(writer.BaseStream);
            System.Xml.XmlValidatingReader vr = new System.Xml.XmlValidatingReader(tr);

            // Open the Schema
            System.IO.Stream schema = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("NHibernate.Mapping.Attributes.nhibernate-mapping.xsd");
            vr.Schemas.Add("urn:nhibernate-mapping-2.2", new System.Xml.XmlTextReader(schema));
            vr.ValidationType = System.Xml.ValidationType.Schema;
            vr.ValidationEventHandler += new System.Xml.Schema.ValidationEventHandler(XmlValidationHandler);

            _stop = false;
            while(vr.Read() && !_stop) // Read to validate (stop at the first error)
               ;
         }
         catch(System.Exception ex)
         {
            Error.Append(ex.ToString()).Append(System.Environment.NewLine + System.Environment.NewLine);
         }
      }


I can confirm that this works and I've successfully been rebuilding my DB since yesterday having my domain model span multiple assemblies. If anyone has any views, I'm all ears.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 25, 2007 6:00 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
NHibernate already supports multiple domain assemblies without any problems. NHibernate.Mapping.Attributes probably doesn't. Feel free to create a feature request for that.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 25, 2007 6:42 am 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

Okay, I will do that. Just a second question, what are the chances that such a feature request will be included (even if I submit a refactored version of the HbmSerializer class?).

What are the other approaches for passing in several assemblies? I was checking this class: NHibernate.Cfg.Configuration, and I could not find any methods that would satisfy passing more then one assembly.

Thanks.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 25, 2007 7:18 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
I don't know, I'm not maintaining NHibernate.Mapping.Attributes.

What do you mean you could not find any methods? There's AddAssembly, there's AddResource, you can just add mapping files from disk. Also, I don't understand what you mean by "passing in" several assemblies.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 25, 2007 12:45 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Quote:
Also, I don't understand what you mean by "passing in" several assemblies.


By that I mean the signature of a method that can take a collection or an array of assemblies as an argument. If you use AddAssembly, that take only one assembly as an argument, then the method will throw an exception saying that it cannot find the class B referenced by the mapping and residing in another other assembly, when resolving class A, where class A resides in the passed in assembly. As far I've seen, the methods looks for hbm.xml files or use GetTypes() on one specific (passed in) assembly.

Code:
configuration.AddAssembly(myAssembly);


So since I would avoid any custom modification of the really nice NHibernate libraries, I wonder which methods you refer to when you say that
Quote:
NHibernate already supports multiple domain assemblies without any problems
.

Here's is a copy of the AddAssembly methods available on the configuration class:

Code:
/// <summary>
      /// Adds all of the assembly's embedded resources whose names end with <c>.hbm.xml</c>.
      /// </summary>
      /// <param name="assemblyName">The name of the assembly to load.</param>
      /// <returns>This configuration object.</returns>
      /// <remarks>
      /// The assembly must be loadable using <see cref="Assembly.Load(string)" />. If this
      /// condition is not satisfied, load the assembly manually and call
      /// <see cref="AddAssembly(Assembly)"/> instead.
      /// </remarks>
      public Configuration AddAssembly(string assemblyName)


Code:
/// <summary>
      /// Adds all of the assembly's embedded resources whose names end with <c>.hbm.xml</c>.
      /// </summary>
      /// <param name="assembly">The assembly.</param>
      /// <returns>This configuration object.</returns>
      /// <remarks>
      /// This method assumes that the <c>hbm.xml</c> files in the Assembly need to be put
      /// in the correct order by NHibernate.  See <see cref="AddAssembly(Assembly, bool)">
      /// AddAssembly(Assembly assembly, bool skipOrdering)</see> for the impacts and reasons
      /// for letting NHibernate order the <c>hbm.xml</c> files.
      /// </remarks>
      public Configuration AddAssembly(Assembly assembly)


Unless I'm really mistaken here, I cannot see how invoking the AddAssembly method passing in my assembly instance as a parameter to the method can solve my problem when I need the method to map several assemblies that together make the DB.

Since I use attributes and don't have any xml files, then using these methods won't help me either. I have to extract the attributes and generate my xml file(s) first, and then it is natural to use the HbmSerializer. And also in this class I have not found any methods that can take an array or a collection of assembly instances as an argument:

Code:
/// <summary> Writes the mapping of all mapped classes of the specified assembly in the specified hbm.xml file. </summary>
      /// <param name="assembly">Assembly used to extract user-defined types containing a valid attribute (can be [Class], [Subclass] or [JoinedSubclass]).</param>
      /// <param name="filePath">Where the xml is written.</param>
      public virtual void Serialize(System.Reflection.Assembly assembly, string filePath)

/// <summary> Writes the mapping of all mapped classes of the specified assembly in the specified stream. </summary>
      /// <param name="stream">Where the xml is written.</param>
      /// <param name="assembly">Assembly used to extract user-defined types containing a valid attribute (can be [Class], [Subclass] or [JoinedSubclass]).</param>
      public virtual void Serialize(System.IO.Stream stream, System.Reflection.Assembly assembly)

/// <summary> Writes the mapping of all mapped classes of the specified assembly in a MemoryStream and returns it. </summary>
      /// <param name="assembly">Assembly used to extract user-defined types containing a valid attribute (can be [Class], [Subclass] or [JoinedSubclass]).</param>
      /// <returns>Stream containing the XML mapping.</returns>
      public virtual System.IO.MemoryStream Serialize(System.Reflection.Assembly assembly)


As I've got mappings of some types to be persisted in assembly G that is common across domains, then many of my classes residing in assembly A references the classes in the general assembly G. Passing in just assembly A will throw an exception saying that referenced type cannot be mapped (or something like that, I don't remember the exact phrasing at the moment). Therefore I wonder what method(s) I should have invoked instead, to use the already existing support for multiple assemblies.

By using the method posted in the first posting of this thread, this case is no problem at all.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 26, 2007 1:23 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
Ah, so you mean mapping files referencing other mapping files in a different assembly. This might not work in the latest version, if you have a test case ready for this problem, please submit a bug report.

Note that NHibernate.Mapping.Attributes is an unsupported library but I can apply a patch you submit if it comes with a test case.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 07, 2007 6:21 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

I have made a small test case and submitted a bug report
(NH-1004) with a possible fix for this problem.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 20, 2007 12:24 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi Sergey!

I saw you filed this case as a duplicate of NH-989! Does this mean that you will not apply such a patch to the HbmSerializer class in NHibernate.Mapping.Attributes? Although the NH-989 case is addressing a similar problem, it will not help me much as I generate my .hbm.xml files and never store them as embedded resources in the assemblies.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 21, 2007 2:50 am 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi Sergey!

Thanks for reopening the minor bug NH-1004.

_________________
Cheers,
Steinar.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.