Last week I received the assignment to look for an alternative to the NHibernate hbm mapping, and decided to give the NHibernate mapping-by-code a try (seen that the fluent NHibernate project hasn't been updated in the last couple of years). Thank to NOtherDev blog and a lot of other questions here on SO, I was able to map almost everything, but I still have a couple of issues.
Issue:
I have an entity that as a Component of type IDocument, and it has a private implementation of this interface, but when I try to map the various properties of this component I get the error Could not find property nor field XXX
, because in the interface this properties do not have the setter.
Interface
public interface IDocument : IObject
{
byte[] DocumentData { get; }
string Name { get; }
DocumentFormats Format { get; }
}
Entity class (to be mapped)
public class ArchiveDocument : /* [...] */, IArchiveDocument
{
private readonly MyDocument _document = new MyDocument();
public IDocument Document
{
get { return _document; }
set
{
ThrowExceptionIfReadonly(nameof(Document));
_document.Format = value.Format;
_document.Name = value.Name;
_document.DocumentData = value.DocumentData;
}
}
// private document implementation
private class MyDocument : IDocument
{
public MyDocument(){ /* constructor */}
private byte[] _documentData;
public byte[] DocumentData
{
get { return _documentData; }
set
{
_documentData = value;
}
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private DocumentFormats _format;
public DocumentFormats Format
{
get { return _format; }
set { _format = value; }
}
}
}
Mapping
public class ArchiveDocumentMap : ClassMapping<ArchiveDocument>
{
Table("DOCUMENT");
Lazy(false);
OptimisticLock(OptimisticLockMode.Version);
Component(d => d.Document, d =>
{
d.Access(Accessor.Field);
d.Property(dd => dd.DocumentData, dd =>
{
dd.Column("DOC_B_DOCUMENT_DATA");
dd.Access(Accessor.Field);
dd.Type<BinaryBlobType>();
dd.Insert(true);
dd.Update(false);
});
d.Property(dd => dd.Format, dd =>
{
dd.Column("DOC_S_FORMAT");
dd.Access(Accessor.Field);
dd.Type<EnumStringType<DocumentFormats>>();
});
d.Property(dd => dd.Name, dd =>
{
dd.Column("DOC_S_NAME");
dd.Access(Accessor.Field);
dd.Type</*own type*/>();
});
});
}
old hbm
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="/*namespace of class*/" assembly="/*namespace of assebly*/.ArchiveDocument">
<class name="ArchiveDocument" table="DOCUMENT" optimistic-lock="version" lazy="false">
<component name="Document" access="field.camelcase-underscore">
<property name="DocumentData" column="DOC_B_DOCUMENT_DATA" access="field.camelcase-underscore" type="BinaryBlob" insert="true" update="false"/>
<property name="Format" column="DOC_S_FORMAT" access="field.camelcase-underscore" type="NHibernate.Type.EnumStringType`1[[/* Enum path */]], NHibernate"/>
<property name="Name" column="DOC_S_NAME" access="field.camelcase-underscore" type="/* Own type (variant of string) */"/>
</component>
</class>
</hibernate-mapping>
Error
Could not find property nor field 'DocumentData' in class '/* Namespace */.IDocument'
From what I understand both, mapping-by-code and Fluent NHibernate, are not "interface friendly", but in my opinion if the hbm file was able to map this class, so should mapping-by-code. Am I doing something wrong, or is there no way to do what I want with mapping-by-code? If the latter, is there a work around?
Edit:
The error is not DocumentData
specific, If I comment it out, the same happens for Name
and Format
.
Edit 2: Complete Stacktrace of inner exception
[PropertyNotFoundException: Could not find property nor field 'DocumentData' in class 'Eis.Framework.Business.Core.Document.IDocument']
NHibernate.Properties.FieldAccessor.GetField(Type type, String fieldName, Type originalType) +220
NHibernate.Properties.FieldAccessor.GetField(Type type, String fieldName, Type originalType) +90
NHibernate.Properties.FieldAccessor.GetGetter(Type theClass, String propertyName) +84
NHibernate.Tuple.Component.PocoComponentTuplizer.BuildGetter(Component component, Property prop) +41
NHibernate.Tuple.Component.AbstractComponentTuplizer..ctor(Component component) +168
NHibernate.Tuple.Component.PocoComponentTuplizer..ctor(Component component) +21
NHibernate.Tuple.Component.ComponentEntityModeToTuplizerMapping..ctor(Component component) +368
NHibernate.Tuple.Component.ComponentMetamodel..ctor(Component component) +301
NHibernate.Mapping.Component.BuildType() +28
NHibernate.Mapping.Component.get_Type() +16
NHibernate.Mapping.SimpleValue.IsValid(IMapping mapping) +30
NHibernate.Mapping.PersistentClass.Validate(IMapping mapping) +87
NHibernate.Mapping.RootClass.Validate(IMapping mapping) +35
NHibernate.Cfg.Configuration.ValidateEntities() +169
NHibernate.Cfg.Configuration.Validate() +13
NHibernate.Cfg.Configuration.BuildSessionFactory() +34
[namespace].UnitOfWorkProviderNHibernate.InitializeImpl(IDictionary`2 parameters) in C:\path\to\file\UnitOfWorkProviderNHibernate.cs:241
[namespace].Initialize(IDictionary`2 parameters) in C:\path\to\file\ProviderBase.cs:36
[namespace].InitializeProvider(ProviderItem providerItem) in C:\path\to\file\BusinessProviderFactory.cs:287
Edit 3:
I tried to see what the generated hbm.xml
file looks like to compare it with the one we are using now, that I know it works, and this are my findings:
- In the component tag the class attribute is filled in (even though I didn't specify it in my Map class)
- The accessors are
field
and notfield.camelcase-underscore
The attribute
insert=true
is missing inDocumentData
(it is explicitly given in my Map class)