I have the following class:
public class MyClass
{
private List<long> _myList = new List<long>();
public virtual string MyID { get; set; }
public virtual string MyData
{
get
{
return SomeStaticClass.Serialize(_myList);
}
set
{
_myList = SomeStaticClass.Deserialize<List<long>>(value);
}
}
public virtual List<long> MyList
{
get { return _myList; }
}
}
And the following mapping file:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyNamespace"
namespace="MyNamespace">
<class name="MyNamespace.MyClass" table="MY_TABLE">
<id name="MyID" column="MY_ID" type="System.String">
<generator class="assigned"></generator>
</id>
<property name="MyData" column="MY_DATA"></property>
</class>
</hibernate-mapping>
When I try to run the following line:
session.Delete("From MyClass m");
I am getting a QuerySyntaxException with the message "MyClass is not mapped [From MyClass s]".
When I change the name of the "MyID" field to "ID" in the mapping file, the exception becomes
NHibernate.PropertyNotFoundException: Could not find a getter for property 'ID' in class 'MyNamespace.MyClass'.
so I am assuming it can find the mapping file. I made sure that the mapping file is an embedded resource, checked and dobule checked the namespace and class names in the mapping file. What may cause the error? I think it may relate to the MyList property which is not mapped but I am not sure since I am using non-mapped properties on my other classes without a problem.
EDIT: I tried overriding this class, with a class which has no "MyData" property and redefining "MyList" property as string. I am still receiving the same error for my overridden class.
EDIT 2: Tried with a very simple class with the same property names with same return types and only simple get; set; blocks. I still get the same error. I am almost sure that nhibernate can see my mapping files because if I change the name of a single property, it gives me PropertyNotFound instead of "class in not mapped".
How are you loading the hbms? If they are resources, make sure you've actually set the files to be embedded resources in Visual Studio
In case of mapping
<class name="MyClass" table="MY_TABLE">
you should use, for example:
session.CreateQuery("from MyClass")
but not:
session.CreateQuery("from MY_TABLE")
what about if you use
session.Delete("From MyNamespace.MyClass m");
I was just looking at the HQL reference and noticed in their cat example they use fully qualified objects, i.e. Eg.Cat.
You should set the related *.hbm.xml as Embedded Resource.
Make sure your "Build Action of the file" is "Embedded Resource".
I had this problem. I forgot to put hbm in the name of mapping XML files.
it seems a bit strange you are specifying the namespace twice in the mapping file. I would try just specifying the name attribute as just "MyClass" instead of "MyNamespace.MyClass" so it would be
<class name="MyClass" table="MY_TABLE">
I had a similar problem like this. Basically, I included a new project into the solution and I did not map the namespace in the hibernate.cfg.xml file.
Related
I just had a NHibernate related problem where I forgot to map one property of a class.
A very simplified example:
public class MyClass
{
public virtual int ID { get; set; }
public virtual string SomeText { get; set; }
public virtual int SomeNumber { get; set; }
}
...and the mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyAssembly"
namespace="MyAssembly.MyNamespace">
<class name="MyClass" table="SomeTable">
<property name="ID" />
<property name="SomeText" />
</class>
</hibernate-mapping>
In this simple example, you can see the problem at once:
there is a property named "SomeNumber" in the class, but not in the mapping file.
So NHibernate will not map it and it will always be zero.
The real class had a lot more properties, so the problem was not as easy to see and it took me quite some time to figure out why SomeNumber always returned zero even though I was 100% sure that the value in the database was != zero.
So, here is my question:
Is there some simple way to find this out via NHibernate?
Like a compiler warning when a class is mapped, but some of its properties are not.
Or some query that I can run that shows me unmapped properties in mapped classes...you get the idea.
(Plus, it would be nice if I could exclude some legacy columns that I really don't want mapped.)
EDIT:
Okay, I looked at everything you proposed and decided to go with the meta-data API...that looks the easiest to understand for me.
Now that I know what to search for, I found some examples which helped me to get started.
So far, I have this:
Type type = typeof(MyClass);
IClassMetadata meta = MySessionFactory.GetClassMetadata(type);
PropertyInfo[] infos = type.GetProperties();
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name))
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
It nearly works, except one thing:
IClassMetadata.PropertyNames returns the names of all the properties except the ID.
To get the ID, I have to use IClassMetadata.IdentifierPropertyName.
Yes, I could save .PropertyNames in a new array, add .IdentifierPropertyName to it and search that array.
But this looks strange to me.
Is there no better way to get all mapped properties including the ID?
You could use the NHibernate meta-data API to find the mapped properties, and reflection to find all the properties.
Edit No, there isn't any other way list all the properties including the id. It isn't that hard to use:
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name) || info.Name = meta.IdentifierPropertyName)
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
There are two tools I'm aware of that can help with this:
Fluent NHibernate persistence specification testing
Nhibernate Ghostbuster
but they don't specifically address the problem you had with an unmapped property. The best solution is to write good unit tests that ensure that the properties you want to persist are persisted correctly. It's tedious but necessary.
I'm having a hard time trying to get my stored procedure works with NHibernate. The data returned from the SP does not correspond to any database table.
This is my mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel" namespace="DomainModel.Entities">
<sql-query name="DoSomething">
<return class="SomeClass">
<return-property name="ID" column="ID"/>
</return>
exec [dbo].[sp_doSomething]
</sql-query>
</hibernate-mapping>
Here is my domain class:
namespace DomainModel.Entities
{
public class SomeClass
{
public SomeClass()
{
}
public virtual Guid ID
{
get;
set;
}
}
}
When I run the code, it fails with
Exception Details: NHibernate.HibernateException: Errors in named queries: {DoSomething}
at line 80
Line 78: config.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NHibernate.config"));
Line 79:
Line 80: g_sessionFactory = config.BuildSessionFactory();
When I debug into NHibernate code, it seems that SomeClass is not added to the persister dictionary because there isn't a class mapping (only sql-query) defined in hbm.xml. And later on in CheckNamedQueries function, it is not able to find the persistor for SomeClass.
I've checked all the obvious things (e.g. make hbm as an embedded resource) and my code isn't too much different from other samples I found on the web, but somehow I just can't get it working. Any idea how I can resolve this issue?
Well, where is your class mapping for SomeClass?
You still need to map it. Read http://nhibernate.info/doc/nh/en/index.html#querysql-load.
Look at using a class mapping with a subselect block. I found this in the Java documentation but maybe it will work for .Net too.
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html (scroll down to section 5.1.3)
I've seen several questions related to properly mapping an enum type using NHibernate.
This article by Jeff Palermo showed me how to do that properly by creating a custom type. I use Schema Export to create my DB during my dev cycles, but this method breaks my export statement. Is there a way to specify the type of the column on export?
Here is my enum code:
public enum OperatorCode
{
CodeA,
CodeB,
CodeC,
CodeD
}
Here is my custom type:
public class OperatorCodeType:EnumStringType
{
public OperatorCodeType():base(typeof(OperatorCode),20)
{
}
}
Here is my property in my mapping file:
<property name="OperatorCode" column="OperatorCode" type="OperatorCodeType" />
And finally here is my class declaration for that property:
public virtual OperatorCode OperatorCode { get; set; }
Is it even possible to do this?
I have not tested it, but you can use the Column declaration within a property to specify the sql type. Example from the docs:
<property name="Foo" type="String">
<column name="foo" length="64" not-null="true" sql-type="text"/>
</property>
Granted this is a string, but you may want to try it with the type of OperatorCodeType, column sql-type as text or nvarchar or whatever works.
If you try it, let me know? Not near my dev machine at the moment.
I'm using v2.1 of NHibernate.dll and NHibernate.Mappings.Attributes v2.1 in a project.
When I run the code further below, I get the following exception, and will be grateful for any pointers. On the same project, if I remove the attributes and use xml mapping files, it works fine.
NHibernate.MappingException was unhandled
Message="Could not compile the mapping document:
DomainModel.hbm.xml"
Source="NHibernate"
InnerException: System.NullReferenceException
Message="Object reference not set to an instance of an object."
Source="NHibernate"
StackTrace:
at NHibernate.Cfg.XmlHbmBinding.ClassBinder.BindClass
(XmlNode node, PersistentClass model)
at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.Bind
(XmlNode node, HbmClass classSchema)
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddRootClasses(XmlNode
parentNode)
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(XmlNode node)
at NHibernate.Cfg.Configuration.AddValidatedDocument(NamedXmlDocument doc)
InnerException:
I have a contact class as follows (Domain class has just one method, no properties):
[NHibernate.Mapping.Attributes.Class]
public class Contact : DomainClass
{
[NHibernate.Mapping.Attributes.Id(Name = "Id")]
[NHibernate.Mapping.Attributes.Generator(1, Class ="Identity")]
public virtual int ID { get; set; }
[NHibernate.Mapping.Attributes.Property]
public virtual string Name { get; set; }
[NHibernate.Mapping.Attributes.Property]
public virtual string Town { get; set; }
}
and session code as follows:
Configuration cfg = new Configuration();
cfg.Configure();
cfg.AddInputStream(NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize(
typeof(Contact).Assembly), "DomainModel.hbm.xml");
_sessionFactory=cfg.BuildSessionFactory();
My hibernate.cfg.xml file is:
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</
property>
<property name="connection.connection_string">Server=SERVER
\EXPRESS2008;Initial Catalog=Contacts;Integrated Security=True</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFac tory, NHibernate.ByteCode.LinFu</property>
</session-factory>
</hibernate-configuration>
Stuart,
As I understand it,"DomainModel.hbm.xml" is the file NHibernate.Mappings.Attributes should create - the exception happens before the file is created (it's not in the output directory) and so unfortunately I can't post it.
Stuart,
Thanks again for your response.
Managed to get it to work using:
using (MemoryStream stream = new MemoryStream())
{
HbmSerializer.Default.HbmNamespace = "NSpace.DomainLayer.Entities";
HbmSerializer.Default.HbmAssembly = "NSpace";
HbmSerializer.Default.Serialize(stream,
System.Reflection.Assembly.GetExecutingAssembly());
stream.Position = 0;
Configuration cfg = new Configuration();
cfg.Configure();
cfg.AddInputStream(stream);
_sessionFactory = cfg.BuildSessionFactory();
}
and specifying table names in class attributes (my oversight as these were different from class names!).
Not sure why we need to specify Namespace separately, as I assumed NHibernate could work out the types to serialize from the assembly.
Hope above helps anyone experiencing similar issue, although my impression is few people are using NHibernate.Mappings.Attributes. The documentation seems to be seriously lacking.
Whenever you use hbm.xml file you will set your configuration class like this:
Configuration cfg = new Configuration();
cfg.Configure();
// Add class mappings to configuration object
cfg.AddAssembly(Assembly.GetCallingAssembly());
ISessionFactory sessionFactory = cfg.BuildSessionFactory();
Whenever you use Nhibernate.Mapping.Attributes like classe you will use:
For example you have use Mapping.attributes in Product Class
Configuration cfg = new Configuration();
cfg.Configure();
// Add class mappings attributes to configuration object
cfg.AddInputStream(HbmSerializer.Default.Serialize(typeof(Model.Product);
ISessionFactory sessionFactory = cfg.BuildSessionFactory();
For a while, NHMA didn't auto detect classes names because they are now optional in NHibernate (in some scenarios, you would use entity-name instead).
The most recent version however restores the auto-detect behavior with a setting to turn that off if needed.
The attributes must be manually ordered because .NET doesn't guarantee it when compiling (unlike Java).
The end goal of NHMA is to faithfully duplicate the way you write the XML version; so if you have:
<something>
<innerData/>
</something>
The NHMA version will be:
[Something]
[InnerData(2)]
NHMA tries to be smart about deducing certain values (like names), but it will only do so if the value is required.
It also provide helpers like NameType=typeof(XXX) to benefit from intellisense, compile time verification and refactoring.
More details in the documentation:
http://www.nhforge.org/doc/nh/en/index.html#mapping-attributes
If you have not solved your problem yet, try this
[NHibernate.Mapping.Attributes.Class(Table="youtable",NameType=typeof(Contact ))]
public class Contact : DomainClass
{
[NHibernate.Mapping.Attributes.Id(Name = "Id")]
[NHibernate.Mapping.Attributes.Generator(1, Class ="Identity")]
public virtual int ID { get; set; }
[NHibernate.Mapping.Attributes.Property(Name="Name")]
public virtual string Name { get; set; }
[NHibernate.Mapping.Attributes.Property(Name="Town")]
public virtual string Town { get; set; }
}
I'm using like this and it works fine .....
I must say that NHibnerate.Mapping.Atributes component was poorly written.
The code below will lead to a generation of malformed mapping xml
[Class()]
public class Bar{}
Meanwhile, the code below is fine:
[Class(Name="Bar")]
public class Bar{}
Further more, if you place [Generator] attribute after [Id] attribute, then the information about generator will not be included in the xml, but place [Generator] before [Id] will do the jobs.
The 3.0 is under working and I hope that these "nasty" bugs will be fixed.
As indicted above, you have to specify the class' Name since version 2...
I have posted an article on how to work around this by deriving the HbmWriter: http://blog.hopla.org/2009/12/fix-for-nhibernate-mapping-attributes-2-1/
Almost copy of mitjast's answer, with minor fixes and formatting:
[Class(NameType = typeof(DomainClass))]
public class DomainClass
{
[NHibernate.Mapping.Attributes.Generator(Class = "guid")]
[NHibernate.Mapping.Attributes.Id(Name = "DomainID")]
public virtual Guid DomainID { get; set; }
[NHibernate.Mapping.Attributes.Property]
public virtual string Name { get; set; }
}
That definition helped me get through all exceptions and generate valid hbm mapping.
As Mr Cold mentioned, the order of Generator and Id attributes does matter.
But in my case it was discovered that while for one class Id should go first to be actually mentioned within hbm, for another class the first attribute should be Generator. When I change the order of these attributes in one class so that both classes have equal order, the hbm for one of them becomes wrong...
The situation did not change after the release of NHibernate.Mapping.Attributes-for-NHibernate-3.0.0.Alpha2.
Presence of such quirky things looks enough for me to switch to another solution unfortunately...
Mapping a collection of enums with NHibernate
Specifically, using Attributes for the mappings.
Currently I have this working mapping the collection as type Int32 and NH seems to take care of it, but it's not exactly ideal.
The error I receive is "Unable to determine type" when trying to map the collection as of the type of the enum I am trying to map.
I found a post that said to define a class as
public class CEnumType : EnumStringType {
public CEnumType() : base(MyEnum) { }
}
and then map the enum as CEnumType, but this gives "CEnumType is not mapped" or something similar.
So has anyone got experience doing this?
So anyway, just a simple reference code snippet to give an example with
[NHibernate.Mapping.Attributes.Class(Table = "OurClass")]
public class CClass : CBaseObject
{
public enum EAction
{
do_action,
do_other_action
};
private IList<EAction> m_class_actions = new List<EAction>();
[NHibernate.Mapping.Attributes.Bag(0, Table = "ClassActions", Cascade="all", Fetch = CollectionFetchMode.Select, Lazy = false)]
[NHibernate.Mapping.Attributes.Key(1, Column = "Class_ID")]
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "Int32")]
public virtual IList<EAction> Actions
{
get { return m_class_actions; }
set { m_class_actions = value;}
}
}
So, anyone got the correct attributes for me to map this collection of enums as actual enums? It would be really nice if they were stored in the db as strings instead of ints too but it's not completely necessary.
You will need to map your CEnum type directly. In XML mappings this would mean creating a new class mapping element in your NHibernate XML mappings file.
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="YourAssembly"
auto-import="true" default-lazy="false">
...
<class name="YourAssemblyNamespace.CEnum" table="CEnumTable" mutable="false" >
<id name="Id" unsaved-value="0" column="id">
<generator class="native"/>
</id>
...
</class>
</hibernate-mapping>
To do it with attribute mappings, something like this on top of your CEnum class:
[NHibernate.Mapping.Attributes.Class(Table = "CEnumTable")] //etc as you require
This is the way i do it. There's probably an easier way but this works for me.
Edit: sorry, i overlooked that you want it as a list. I don't know how to do that...
Edit2: maybe you can map it as a protected IList[string], and convert to public IList[EAction] just as i do with a simple property.
public virtual ContractGroups Group
{
get
{
if (GroupString.IsNullOrEmpty())
return ContractGroups.Default;
return GroupString.ToEnum<ContractGroups>(); // extension method
}
set { GroupString = value.ToString(); }
}
// this is castle activerecord, you can map this property in NH mapping file as an ordinary string
[Property("`Group`", NotNull = true)]
protected virtual string GroupString
{
get;
set;
}
/// <summary>
/// Converts to an enum of type <typeparamref name="TEnum"/>.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="self">The self.</param>
/// <returns></returns>
/// <remarks>From <see href="http://www.mono-project.com/Rocks">Mono Rocks</see>.</remarks>
public static TEnum ToEnum<TEnum>(this string self)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
Argument.SelfNotNull(self);
return (TEnum)Enum.Parse(typeof(TEnum), self);
}
instead of
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "Int32")]
try
[NHibernate.Mapping.Attributes.Element(2, Column = "EAction", Type = "String")]
ie: change the Int32 to String
While I haven't tried using it myself, I stumbled across this code a little while ago and it looks pretty interesting:
http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/08/12/enumeration-classes.aspx
Like I said, I haven't used it myself, but I'm going to give it a go in a project RSN.