NHibernate QueryOver projections, enums and aliastobean transformers - nhibernate

How do I transform an Enum value into a String Value using QueryOver and AliasToBean? I have the following but get an error when trying to transform the Enum:
SomeDTO someDTO = null;
SomeReferenceAlias someReferenceAlias = null;
var jobs = query
.JoinAlias(x => x.SomeReference, () => someReferenceAlias, JoinType.InnerJoin)
.SelectList(list => list
.Select(p => p.SomeStatusEnum).WithAlias(() => someDTO.SomeStatus)//problem here
.Select(p => someReferenceAlias.Name).WithAlias(() => someDTO.Name)
)
.TransformUsing(Transformers.AliasToBean<SomeDTO>())
.Take(100)
.List<SomeDTO>();

Assuming your enum is stored as int in your DB, I would try a string readonly property to a custom string type :
public enum SomeStatus {up=1,right=2,down=3,left=4}
public class SomeStatusNhString : NHibernate.Type.AbstractStringType
{
public SomeStatusNhString()
: base(new StringSqlType())
{
}
public SomeStatusNhString(StringSqlType sqlType)
: base(sqlType)
{
}
public override string Name
{
get { return "SomeStatusNhString"; }
}
public override object Get(System.Data.IDataReader rs, int index)
{
var x = base.Get(rs, index);
return ((SomeStatus)int.Parse((string)x)).ToString();
}
}
And then your mapping
public virtual String StatusAsString{ get; set; }
<property name="StatusAsString" column="YOUR_COLUMN" not-null="true" insert="false" update="false" type="YourNameSpace.SomeStatusNhString, YourDll" access="property"></property>
Hope this can help

Related

NHibernate: single lazy load property

Update: I'm now convinced that the problem lies in the fact that Document is configured as non lazy. The problem is that I don't control the base class and that means I can't change the base props to virtual...
After reading the docs, I'm under the assumption that I should be able to have a non lazy class with a lazy property. Is this possible? Here's the code I'm using for mapping my class:
public class DocumentoMapping : ClassMap<Documento> {
public DocumentoMapping()
{
Setup();
}
private void Setup()
{
Table("Documentos");
Not.LazyLoad();
Id(doc => doc.Id, "IdDocumentos")
.GeneratedBy.Identity()
.Default(0);
Map(doc => doc.NomeDocumento)
.Not.Nullable();
Map(doc => doc.Descricao);
Map(doc => doc.Bytes, "Documento")
.CustomSqlType("image")
.CustomType<Byte[]>()
.LazyLoad()
.Length(2000000000);
Component(doc => doc.Acao,
accao =>
{
accao.Map(a => a.Login);
accao.Map(a => a.Data);
accao.Map(a => a.UserAD)
.CustomSqlType("int")
.CustomType<ADs>();
})
.Not.LazyLoad();
Map(doc => doc.IdPedidoAssistencia)
.Column("IdPats")
.Not.LazyLoad();
}
}
And here's the code for my class:
public class Documento : Entity, IHasAssignedId<int>{
public virtual Byte[] Bytes { get; private set; }
public Documento()
{
NomeDocumento = Descricao = "";
Acao = new Acao("none", DateTime.Now, ADs.Sranet);
}
public Documento(string nomeDocumento, string descricao, Acao acao)
{
Contract.Requires(!String.IsNullOrEmpty(nomeDocumento));
Contract.Requires(!String.IsNullOrEmpty(descricao));
Contract.Requires(acao != null);
Contract.Ensures(!String.IsNullOrEmpty(NomeDocumento));
Contract.Ensures(!String.IsNullOrEmpty(Descricao));
Contract.Ensures(Acao != null);
NomeDocumento = nomeDocumento;
Descricao = descricao;
Acao = acao;
}
[DomainSignature]
public String NomeDocumento { get; private set; }
[DomainSignature]
public String Descricao { get; private set; }
[DomainSignature]
public Acao Acao { get; private set; }
internal Int32 IdPedidoAssistencia { get; set; }
internal static Documento CriaNovo(String nomeDocumento, String descricao, Byte[] bytes, Acao acao)
{
Contract.Requires(!String.IsNullOrEmpty(nomeDocumento));
Contract.Requires(!String.IsNullOrEmpty(descricao));
Contract.Requires(bytes != null);
Contract.Requires(acao != null);
var documento = new Documento(nomeDocumento, descricao, acao) { Bytes = bytes };
return documento;
}
public void ModificaBytes(Byte[] bytes)
{
Contract.Requires(bytes != null);
Bytes = bytes;
}
public void SetAssignedIdTo(int assignedId)
{
Id = assignedId;
}
[ContractInvariantMethod]
private void Invariants()
{
Contract.Invariant(NomeDocumento != null);
Contract.Invariant(Descricao != null);
Contract.Invariant(Acao != null);
}
}
Base classes are the just for the basic stuff, ie, setting Id and injecting base code for instance comparison. At first sight, I can't really see anything wrong with this code. I mean, the property is virtual, the mapping says it should be virtual, so why does loading it with Get forces a complete load of the properties? For instance, this code:
var d = sess.Get(148);
Ends up generating sql for loading all the properties on the table. Did I get this wrong?
thanks!
Yes, it's confirmed: in order to have lazy load properties on a class, the class will also need to be lazy.

Auditing user using NHibernate Envers fluentconfiguration

I am trying to use NHibernate Envers to log an additional field "user". I have followed several code examples that seem to vary a bit when it comes to syntax, probably because some of them are a bit out of date. However I can't get it to work.
I'm getting this exception:
Only one property may have the attribute [RevisionNumber]!
My Custom Revision Entity:
public class CustomRevisionEntity
{
public virtual int Id { get; set; }
public virtual DateTime RevisionTimestamp { get; set; }
public virtual Guid UserIdentityId { get; set; }
public override bool Equals(object obj)
{
if (this == obj) return true;
var revisionEntity = obj as CustomRevisionEntity;
if (revisionEntity == null) return false;
var that = revisionEntity;
if (Id != that.Id) return false;
return RevisionTimestamp == that.RevisionTimestamp;
}
public override int GetHashCode()
{
var result = Id;
result = 31 * result + (int)(((ulong)RevisionTimestamp.Ticks) ^ (((ulong)RevisionTimestamp.Ticks) >> 32));
return result;
}
}
My IRevisionListener:
public class RevInfoListener : IRevisionListener
{
public void NewRevision(object revisionEntity)
{
var casted = revisionEntity as CustomRevisionEntity;
if (casted != null)
{
casted.UserIdentityId = Guid.NewGuid(); // TODO
}
}
}
First I use mapping by code to map the entity:
_modelMapper.Class<CustomRevisionEntity>(entity =>
{
entity.Property(x => x.Id);
entity.Property(x => x.RevisionTimestamp);
entity.Property(x => x.UserIdentityId);
});
Then I configure Envers and NHibernate
var enversConf = new FluentConfiguration();
enversConf.SetRevisionEntity<CustomRevisionEntity>(x => x.Id, x => x.RevisionTimestamp, new RevInfoListener());
enversConf.Audit<OrganizationEntity>().Exclude(x => x.Version);
configuration.IntegrateWithEnvers(enversConf); // This is the nh-configuration
The last line gives me the exception:
Only one property may have the attribute [RevisionNumber]!
Anyone have any ideas? Myself I would speculate that the default revision entity is still used somehow and when I try to register my custom revision entity this happens.
The error message occurred because the Id property was being mapped twice.
In our mapping class we had this
_modelMapper.BeforeMapClass += (modelInspector, type, classCustomizer) => classCustomizer.Id(type.GetProperty("Id"), (idMapper) =>
{
idMapper.Access(Accessor.Property);
idMapper.Generator(Generators.GuidComb);
});
Then we tried mapping Id again as a property of the CustomRevisionEntity
The final mapping:
_modelMapper.Class<CustomRevisionEntity>(entity =>
{
entity.Id<int>(x => x.Id, mapper => mapper.Generator(Generators.Identity));
entity.Property(x => x.RevisionDate);
entity.Property(x => x.UserIdentityId);
});

Mapping a struct as Id in nhibernate

I have the following struct and class:
public struct DepartmentId{
public int Value {get; set;}
}
public class Department{
public virtual DepartmentId Id{get;set;}
public virtual string Name {get; set;}
}
I created a mapping file for Department as follows:
public class DepartmentMapping : ClassMapping<Department>{
public DepartmentMapping{
Table("Department");
Id(dept => dept.Id, mapper => {
mapper.Column("Id");
mapper.Type(new DepartmentIdType());
});
Property(dept => dept.Name, mapper => mapper.Column("Name"));
}
}
where DepartmentIdType implements IIdentifierType:
class DepartmentIdType : PrimitiveType, IIdentifierType
{
public DepartmentIdType() : base(SqlTypeFactory.Int32)
{
}
public override object DeepCopy(object val, EntityMode entityMode, ISessionFactoryImplementor factory)
{
return val;
}
public override object Replace(object original, object current, ISessionImplementor session, object owner, IDictionary copiedAlready)
{
return original;
}
public override Type ReturnedClass
{
get { return typeof(DepartmentId); }
}
public object StringToObject(string xml)
{
return new DepartmentId {Value = int.Parse(xml)};
}
public override string Name
{
get { return typeof(DepartmentId).Name; }
}
public override void Set(IDbCommand cmd, object value, int index)
{
var id = (DepartmentId) value;
((IDataParameter) cmd.Parameters[index]).Value = id.Value;
}
public override object Get(IDataReader rs, int index)
{
int value = rs.GetInt32(index);
return new DepartmentId {Value = value};
}
public override object Get(IDataReader rs, string name)
{
return Get(rs, rs.GetOrdinal(name));
}
public override string ToString(object val)
{
if (val == null) return "";
return val.ToString();
}
public override object FromStringValue(string xml)
{
return new DepartmentId {Value = Int32.Parse(xml)};
}
public override object DefaultValue
{
get { return new DepartmentId {Value = 0}; }
}
public override string ObjectToSQLString(object value, NHibernate.Dialect.Dialect dialect)
{
return value.ToString();
}
public override Type PrimitiveClass
{
get { return typeof(DepartmentId); }
}
}
However, at the time of creating the HbmMapping, I get the following error:
Could not compile the mapping document: mapping_by_code
NHibernate.MappingException: Could not compile the mapping document:
mapping_by_code ---> NHibernate.MappingException:
Could not determine type for: Demo.Models.DepartmentId, Demo.Models,
for columns: NHibernate.Mapping.Column(Id)
at NHibernate.Mapping.SimpleValue.get_Type()
at NHibernate.Cfg.XmlHbmBinding.ClassIdBinder.CreateIdentifierProperty(HbmId idSchema, PersistentClass rootClass, SimpleValue id)
at NHibernate.Cfg.XmlHbmBinding.ClassIdBinder.BindId(HbmId idSchema, PersistentClass rootClass, Table table)
at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.Bind(HbmClass classSchema, IDictionary`2 inheritedMetas)
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddRootClasses(HbmClass rootClass, IDictionary`2 inheritedMetas)
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddEntitiesMappings(HbmMapping mappingSchema, IDictionary`2 inheritedMetas)
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(HbmMapping mappingSchema)
at NHibernate.Cfg.Configuration.AddDeserializedMapping(HbmMapping mappingDocument, String documentFileName)
How do I fix this issue (without changing DepartmentId from struct to class)?
Thanks in advance for your help.
I suggest using class instead of struct and ComponentAsId instead of Id method for this case. If you don't use id generator it is a straightforward approach without any hacking.
public class DepartmentMapping : ClassMapping<Department> {
public DepartmentMapping{
Table("Department");
ComponentAsId(dept => dept.Id);
Property(dept => dept.Name, mapper => mapper.Column("Name"));
}}
public class DepartmentIdMapping : ComponentMapping<DepartmentId> {
public DepartmentIdMapping{
Property(x=> x.Value, mapper => mapper.Column("Id"));
}}
I tried your approach when investigating this strong typed ids together with id generation but I finally decided to implement custom Hi/Lo generator and use NHibernate assigned ids.
I'm just going to put this here for a reference.
In XML mapping, I did this through mapping as a composite id:
<class name"Department" ... >
<composite-id name="Id" access="property"
class="DepartmentId">
<key-property name="Value"
column="Id"
access="property"
type="System.Int32"/>
</composite-id>
... other stuff
</class>

QueryOver<A>().Where(a => a.B.Count() > 0) does not work

I get an exception: Unrecognised method call in epression a.B.Count() when I run:
var query = session.QueryOver<A>()
.Where(a => a.B.Count() > 0)
.List();
The following code works:
var query1 = session.QueryOver<A>().List();
var query2 = query1.Where(a => a.B.Count() > 0);
Any ideas? Thanks.
Edit:
Here is my mappings. I'm using NHibernate 3.1.0.4000:
Models:
public class A
{
public virtual int Id { get; private set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public virtual int Id { get; private set; }
}
Mappings:
public class AMappings : ClassMap<A>
{
public AMappings()
{
Id(x => x.Id);
HasMany(x => x.Bs).LazyLoad();
}
}
public class BMappings : ClassMap<B>
{
public BMappings()
{
Id(x => x.Id);
}
}
Rest of my code:
class Program
{
static void Main(string[] args)
{
// Create connection string
string connectionString = new System.Data.SqlClient.SqlConnectionStringBuilder()
{
DataSource = #".\r2",
InitialCatalog = "TestNHibernateMappings",
IntegratedSecurity = true
}.ConnectionString;
// Create SessionFactory
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2008.ConnectionString(connectionString)
.ShowSql())
.Mappings(m => m.FluentMappings
.Add(typeof(AMappings))
.Add(typeof(BMappings)))
.ExposeConfiguration(BuildSchema)
.BuildConfiguration()
.BuildSessionFactory();
// Test
var session = sessionFactory.OpenSession();
// This line works OK
var query1 = session.Query<A>()
.Where(a => a.Bs.Count() > 0);
// This line throws exception: Unrecognised method call in epression a.Bs.Count()
var query2 = session.QueryOver<A>()
.Where(a => a.Bs.Count() > 0);
}
static void BuildSchema(Configuration cfg)
{
new SchemaExport(cfg).Create(false, true);
}
}
QueryOver is not LINQ.
Your second code snippet works because it's retrieving ALL THE RECORDS and using LINQ-to-objects in memory.
What you should do is:
session.Query<A>()
.Where(a => a.B.Count() > 0)
.ToList();
or better yet:
session.Query<A>()
.Where(a => a.B.Any())
.ToList();
Query is an extension method, you need to add using NHibernate.Linq;

How to change the column order in a multi-column index using fluent-nhibernate?

How to change the column order in a multi-column index?
I.e:
mapping.References(x => x.SomeReference).SetAttribute("index", "IX_index");
mapping.Map(x => x.SomeField).SetAttribute("index", "IX_index");
Produces the following Schema:
create index IX_index on ApplicantProgramDatas (SomeField, SomeReferenceId)
But I want to get:
create index IX_index on ApplicantProgramDatas (SomeReferenceId, SomeField)
You can define an index in NHibernate using <database-object> or IAuxiliaryDatabaseObject.
In a hbm.xml file:
<hibernate-mapping xmlns="urn:nhiernate-mapping-2.2">
<database-object>
<create>VALID SQL</create>
<drop>VALID SQL</create>
</database-object>
</hibernate-mapping>
N.B. <database-object> can go before or after a class mapping in the same hbm.xml file allowing you to keep your index definitions, triggers, etc. with the object to which they apply.
The other option is NHibernate.Mapping.IAuxiliaryDatabaseObject:
namespace NHibernate.Mapping {
public interface IAuxiliaryDatabaseObject : IRelationalModel {
void AddDialectScope(string dialectName);
bool AppliesToDialect(Dialect dialect);
void SetParameterValues(IDictionary<string, string> parameters);
}
public interface IRelationalModel {
string SqlCreateString(Dialect dialect, IMapping p, string defaultCatalog, string defaultSchema);
string SqlDropString(Dialect dialect, string defaultCatalog, string defaultSchema);
}
}
Given that you're using Fluent NHibernate, IAuxiliaryDatabaseObject will probably work better for you. Just expose your configuration when building it and then call:
var sqlCreate = "CREATION SCRIPT";
var sqlDrop = "DROP SCRIPT";
cfg.AddAuxiliaryDatabaseObject(new SimpleAuxiliaryDatabaseObject(sqlCreate, sqlDrop));
N.B. NHibernate.Mapping.SimpleAuxiliaryDatabaseObject is part of NHibernate. You don't have to write it yourself if all you need to do is supply create/drop scripts for a database object.
I took a quick look in the Fluent NHibernate codebase and didn't see any direct support for IAuxiliaryDatabaseObject. So it is a matter of exposing your configuration object and supplying all your IAuxiliaryDatabaseObjects yourself. It wouldn't be too difficult to write some code that scan through your mapping assembly looking for types that implement IAuxiliaryDatabaseObject and then foreach'ing over them to pass to cfg.AddAuxiliaryDatabaseObject(obj).
You can find more information about auxiliary database objects in the NHibernate docs:
http://nhibernate.info/doc/nh/en/index.html#mapping-database-object
I guess it is impossible. 'FluentNHibernate.MappingModel.MappedMembers.AcceptVisitor()' iterates properties before references:
foreach (var collection in Collections)
visitor.Visit(collection);
foreach (var property in Properties)
visitor.Visit(property);
foreach (var reference in References)
visitor.Visit(reference);
As a result, you will always have properties before references in multi-column index.
BTW none of the ORMs will give you ability to set non-trivial index options like clustered, filtered, etc.
Let me suggest to override SchemaExport. There is private field accessor via reflection, it requires full trust mode. If this approach does not suit your needs, consider rewriting SchemaExport (relatively light class)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Mapping;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
namespace FluentNHib
{
public class Master
{
public int Id { get; set; }
}
public class Child
{
public int Id { get; set; }
[MCIndex("A", 0)]
public Master Master { get; set; }
[MCIndex("A", 1)]
public string Name { get; set; }
}
public class MCIndexAttribute : Attribute
{
public string indexName;
public int indexOrder;
public MCIndexAttribute(string indexName, int i)
{
this.indexName = indexName;
this.indexOrder = i;
}
}
public class MasterMap : ClassMap
{
public MasterMap()
{
Id(x => x.Id);
}
}
public class ChildMap : ClassMap
{
public ChildMap()
{
Id(x => x.Id);
References(x => x.Master).Index("A");
Map(x => x.Name).Index("A");
}
}
class MySchemaExport : SchemaExport
{
internal struct MCIndexField
{
internal int index;
internal string Name;
}
internal class MCIndex
{
internal string IndexName;
public readonly IList fields = new List();
public string Table;
public void AddField(string name, int indexOrder)
{
fields.Add(new MCIndexField {index = indexOrder, Name = name});
}
}
private readonly Dictionary indexes = new Dictionary();
MCIndex ByName(string name, string table)
{
MCIndex result;
if (!indexes.TryGetValue(name, out result))
{
result = new MCIndex
{
IndexName = name
};
indexes.Add(name, result);
}
return result;
}
public MySchemaExport(Configuration cfg) : base(cfg)
{
foreach (var type in typeof(ChildMap).Assembly.GetTypes())
{
foreach (var prop in type.GetProperties())
{
var attr = prop.GetCustomAttributes(typeof (MCIndexAttribute), true);
if (attr.Length == 1)
{
var attribute = (MCIndexAttribute) attr[0];
ByName(attribute.indexName, type.Name).AddField(prop.Name, attribute.indexOrder);
}
}
}
var createSqlProp = typeof(SchemaExport).GetField("createSQL", BindingFlags.NonPublic | BindingFlags.Instance);
var wasSql = createSqlProp.GetValue(this);
var sb = new StringBuilder();
sb.AppendLine("");
foreach (var mcIndex in indexes)
{
sb.AppendLine(string.Format("create index {0} on {1} ({2})", mcIndex.Value.IndexName, mcIndex.Value.Table, mcIndex.Value.fields));
}
createSqlProp.SetValue(this, wasSql + sb.ToString());
}
}
class Program
{
private static void BuildSchema(Configuration config)
{
new MySchemaExport(config)
.Create(s =>
{
Debug.WriteLine(s);
}, true);
}
const string fileName = "c:\\temp\\temp.fdb";
private static string GetConnectionString()
{
const string userName = "sysdba";
const string password = "masterkey";
return String.Format("ServerType=1;User={0};Password={1};Dialect=3;Database={2}", userName, password, fileName);
}
private static FluentConfiguration Configurate()
{
var fbc = new FirebirdConfiguration();
return Fluently.Configure()
.Database(fbc.ShowSql().ConnectionString(GetConnectionString()))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf()
)
.ExposeConfiguration(BuildSchema);
}
static void Main(string[] args)
{
FluentConfiguration fluentConfiguration = Configurate();
Configuration cfg = fluentConfiguration.BuildConfiguration();
}
}
}