my application has the following database structure:
Transactions:
- TransactionID (PK, Identity)
- Type
- TotalAmount
TransactionDetails:
- TransactionDetailID (PK, Identity)
- TransactionID (PK)
- Amount
ProductTransactions:
- TransactionID (PK, FK)
- Discount
ProductTransactionDetails:
- TransactionDetailID (PK, FK)
- ProductID (FK)
I have this mapped using Fluent NHibernate so that ProductTransaction inherits from Transaction and uses a SubclassMap. I did the same for ProductTransactionDetail and TransactionDetail. I also have a property called "Details" which is a list of TransactionDetail on my Transaction entity with the following mapping:
HasMany(x => x.Details)
.KeyColumn("TransactionID")
.Inverse()
.Cascade.All();
I'd like to be able to override this on my ProductTransaction entity. When using virtual and override the compiler complained but new virtual seemed to work. The problem i have is how i map this since the ProductTransactionDetails doesn't have the TransactionID column in the table. It needs to somehow grab it from the parent table but i'm not sure how to do this.
I'd appreciate it if someone could help fix the issue i'm having or let me know if i'm going about things in the wrong way.
Thanks
Comments are in the code...
Domain Model
public class Product : IEquatable<Product>
{
protected internal virtual int Id { get; set; }
public virtual bool Equals(Product other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Id == Id;
}
#region Implementation of IEquatable
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Product)) return false;
return Equals((Product) obj);
}
public override int GetHashCode()
{
return Id;
}
public static bool operator ==(Product left, Product right)
{
return Equals(left, right);
}
public static bool operator !=(Product left, Product right)
{
return !Equals(left, right);
}
#endregion Implementation of IEquatable
}
public class Transaction : IEquatable<Transaction>
{
private IList<TransactionDetail> details;
// This is declared protected because it is an implementation
// detail that does not belong in the public interface of the
// domain model. It is declared internal so the fluent mapping
// can see it.
protected internal virtual int Id { get; set; }
public virtual double TotalAmount { get; set; }
// This is declared as a IList even though it is recommended
// to use ICollection for a Bag because the the Testing Framework
// passes a HashSet to NHibernate and NHibernate attempts to cast
// it to a List since it is declared a Bag in the mapping.
public virtual IList<TransactionDetail> Details
{
// I lazily initialize the collection because I do not like
// testing for nulls all through my code but you may see
// issues with this if you use Cascade.AllDeleteOrphan in
// the mapping.
get { return details ?? (details = new List<TransactionDetail>()); }
set { details = value; }
}
#region Implementation of IEquatable
// Do not forget to declare this function as virtual or you will
// get a mapping exception saying that this class is not suitable
// for proxying.
public virtual bool Equals(Transaction other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Id == Id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(Transaction)) return false;
return Equals((Transaction)obj);
}
public override int GetHashCode()
{
return Id;
}
public static bool operator ==(Transaction left, Transaction right)
{
return Equals(left, right);
}
public static bool operator !=(Transaction left, Transaction right)
{
return !Equals(left, right);
}
#endregion Implementation of IEquatable
}
public class TransactionDetail : IEquatable<TransactionDetail>
{
protected internal virtual int Id { get; set; }
public virtual double Amount { get; set; }
#region Implementation of IEquatable
public virtual bool Equals(TransactionDetail other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Id == Id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (TransactionDetail)) return false;
return Equals((TransactionDetail) obj);
}
public override int GetHashCode()
{
return Id;
}
public static bool operator ==(TransactionDetail left, TransactionDetail right)
{
return Equals(left, right);
}
public static bool operator !=(TransactionDetail left, TransactionDetail right)
{
return !Equals(left, right);
}
#endregion Implementation of IEquatable
}
public class ProductTransaction : Transaction, IEquatable<ProductTransaction>
{
public virtual double Discount { get; set; }
// This is declared 'new' because C# does not support covariant
// return types until v4.0. This requires clients to explicitly
// cast objects of type Transaction to ProductTransaction before
// invoking Details. Another approach would be to change the
// property's name (e.g., ProductDetails) but this also requires
// casting.
public virtual new IList<ProductTransactionDetail> Details
{
get { return base.Details.OfType<ProductTransactionDetail>().ToList(); }
set { base.Details = null == value ? null : value.Cast<TransactionDetail>().ToList(); }
}
#region Implementation of IEquatable
public virtual bool Equals(ProductTransaction other)
{
return base.Equals(other);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj as ProductTransaction);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(ProductTransaction left, ProductTransaction right)
{
return Equals(left, right);
}
public static bool operator !=(ProductTransaction left, ProductTransaction right)
{
return !Equals(left, right);
}
#endregion Implementation of IEquatable
}
public class ProductTransactionDetail : TransactionDetail, IEquatable<ProductTransactionDetail>
{
public virtual Product Product { get; set; }
#region Implementation of IEquatable
public virtual bool Equals(ProductTransactionDetail other)
{
return base.Equals(other);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj as ProductTransactionDetail);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(ProductTransactionDetail left, ProductTransactionDetail right)
{
return Equals(left, right);
}
public static bool operator !=(ProductTransactionDetail left, ProductTransactionDetail right)
{
return !Equals(left, right);
}
#endregion Implementation of IEquatable
}
Fluent Mapping
internal sealed class ProductMap : ClassMap<Product>
{
internal ProductMap()
{
Table("Product")
;
LazyLoad()
;
Id(x => x.Id)
.Column("ProductId")
.GeneratedBy.Identity()
;
}
}
internal sealed class TransactionMap : ClassMap<Transaction>
{
internal TransactionMap()
{
// The table name is surrounded by back ticks because
// 'Transaction' is a reserved word in SQL. On SQL Server,
// this translates to [Transaction].
Table("`Transaction`")
;
LazyLoad()
;
Id(x => x.Id)
.Column("TransactionId")
.GeneratedBy.Identity()
;
Map(x => x.TotalAmount)
.Column("TotalAmount")
.Not.Nullable()
;
// You should consider treating TransactionDetail as a value
// type that cannot exist outside a Transaction. In this case,
// you should mark the relation as Not.Inverse and save or
// update the transaction after adding a detail instead of
// saving the detail independently.
HasMany(x => x.Details)
.KeyColumn("TransactionID")
.Cascade.All()
.Not.Inverse()
.AsBag()
;
// You have a Type column in your example, which I took to
// mean that you wanted to use the Table Per Hierarchy
// strategy. It this case you need to inform NHibernate
// which column identifies the subtype.
DiscriminateSubClassesOnColumn("Type")
.Not.Nullable()
;
}
}
internal sealed class TransactionDetailMap : ClassMap<TransactionDetail>
{
internal TransactionDetailMap()
{
Table("TransactionDetail")
;
LazyLoad()
;
Id(x => x.Id)
.Column("TransactionDetailId")
.GeneratedBy.Identity()
;
Map(x => x.Amount)
.Column("Amount")
.Not.Nullable()
;
}
}
internal sealed class ProductTransactionMap : SubclassMap<ProductTransaction>
{
internal ProductTransactionMap()
{
KeyColumn("TransactionId")
;
// I recommend giving the discriminator column an explicit
// value for a subclass. Otherwise, NHibernate uses the fully
// qualified name of the class including the namespace. If
// you later move the class to another namespace or rename
// the class then you will have to migrate all of the data
// in your database.
DiscriminatorValue("TransactionKind#product")
;
Map(x => x.Discount)
.Column("Discount")
.Nullable()
;
// Do not map the over-ridden version of
// the Details property. It is handled
// by the base class mapping.
}
}
internal sealed class ProductTransactionDetailMap : SubclassMap<ProductTransactionDetail>
{
internal ProductTransactionDetailMap()
{
// There was no Type column in your example for this table,
// whcih I took to mean that you wished to use a Table Per
// Class strategy. In this case, you need to provide the
// table name even though it is a subclass.
Table("ProductTransactionDetail")
;
KeyColumn("TransactionDetailId")
;
References(x => x.Product)
.Column("ProductId")
.Not.Nullable()
;
}
}
Unit Tests
[TestClass]
public class UnitTest1
{
private static ISessionFactory sessionFactory;
private static Configuration configuration;
[TestMethod]
public void CanCorrectlyMapTransaction()
{
using (var dbsession = OpenDBSession())
{
var product = new Product();
dbsession.Save(product);
new PersistenceSpecification<Transaction>(dbsession)
.CheckProperty(t => t.TotalAmount, 100.0)
.CheckComponentList(
t => t.Details,
new[] {
new TransactionDetail {
Amount = 75.0,
},
new ProductTransactionDetail {
Amount = 25.0,
Product = product,
},
}
)
.VerifyTheMappings()
;
}
}
private static Configuration Configuration
{
get
{
return configuration ?? (
configuration = forSQLite().Mappings(
m => m.FluentMappings
.Conventions.Setup(x => x.Add(AutoImport.Never()))
.Add(typeof(ProductMap))
.Add(typeof(ProductTransactionMap))
.Add(typeof(ProductTransactionDetailMap))
.Add(typeof(TransactionMap))
.Add(typeof(TransactionDetailMap))
)
.BuildConfiguration()
);
}
}
private static ISessionFactory SessionFactory
{
get { return sessionFactory ?? (sessionFactory = Configuration.BuildSessionFactory()); }
}
private static ISession OpenDBSession()
{
var session = SessionFactory.OpenSession();
// Ideally, this would be done once on the database
// session but that does not work when using SQLite as
// an in-memory database. It works in all other cases.
new SchemaExport(configuration)
.Execute(
true, // echo schema to Console
true, // create schema on connection
false, // just drop do not create
session.Connection, // an active database connection
null // writer for capturing schema
);
return session;
}
private static FluentConfiguration forSQLite()
{
return Fluently.Configure()
.Database(
SQLiteConfiguration
.Standard
.InMemory()
.ShowSql()
);
}
}
Related
I have a base class called Entity:
public class Entity
{
public int Id {get;set;}
}
Let's say I have a class called Customer:
public class Customer : Entity
{
public string Name {get;set;}
}
Now, using convention based mapping by code in NHibernate 3.3.1, I try the following:
public static class DataHelper
{
private static HbmMapping GetMappings()
{
var mapper = new CustomModelMapper(typeof(Entity));
return mapper.CompileMappingFor(
typeof(DataHelper).Assembly.GetExportedTypes()
.Where(x => x.IsSubclassOf(typeof(Entity))));
}
}
When I try to run my app, I get the error "Cannot extend unmapped class: Entity". I don't want to map the Entity class - it's just a base class for inheriting some common properties. How can I tell NHibernate to ignore the unmapped class? For reference, my CustomModelMapper class is listed below.
The code for my CustomModelMapper class is listed below for reference
internal class CustomModelMapper : ConventionModelMapper
{
private const int DEFAULT_STRING_LENGTH = 100;
private Type baseType;
public CustomModelMapper(Type baseType)
{
this.baseType = baseType;
}
public CustomModelMapper()
{
SetupInspectors();
}
protected override void AppendDefaultEvents()
{
base.AppendDefaultEvents();
BeforeMapClass += OnBeforeMapClass;
BeforeMapProperty += OnBeforeMapProperty;
BeforeMapManyToOne += OnBeforeMapManyToOne;
BeforeMapBag += OnBeforeMapBag;
BeforeMapList += OnBeforeMapList;
BeforeMapSet += OnBeforeMapSet;
}
protected void OnBeforeMapClass(IModelInspector modelInspector, Type type, IClassAttributesMapper classCustomizer)
{
classCustomizer.Id(type.GetProperty("Id"), m => m.Generator(Generators.Native));
}
protected void OnBeforeMapProperty(IModelInspector modelInspector, PropertyPath member, IPropertyMapper propertyCustomizer)
{
if (member.LocalMember.GetPropertyOrFieldType().IsEnum)
{
var type = member.LocalMember.GetPropertyOrFieldType();
var genericType = typeof(EnumStringType<>).MakeGenericType(type);
propertyCustomizer.Type(genericType, null);
}
if (member.LocalMember.GetPropertyOrFieldType() == typeof(string))
propertyCustomizer.Length(DEFAULT_STRING_LENGTH);
}
protected void OnBeforeMapManyToOne(IModelInspector modelInspector, PropertyPath member, IManyToOneMapper propertyCustomizer)
{
propertyCustomizer.Cascade(Cascade.All);
propertyCustomizer.Fetch(FetchKind.Join);
propertyCustomizer.Lazy(LazyRelation.NoLazy);
propertyCustomizer.Index(string.Format("IX{0}{1}",
member.GetContainerEntity(modelInspector).Name,
member.LocalMember.Name));
}
protected void OnBeforeMapBag(IModelInspector modelInspector, PropertyPath member, IBagPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Cascade(Cascade.All);
propertyCustomizer.Lazy(CollectionLazy.Extra);
propertyCustomizer.Fetch(CollectionFetchMode.Subselect);
}
protected void OnBeforeMapList(IModelInspector modelInspector, PropertyPath member, IListPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Cascade(Cascade.All);
propertyCustomizer.Lazy(CollectionLazy.Extra);
propertyCustomizer.Fetch(CollectionFetchMode.Subselect);
}
protected void OnBeforeMapSet(IModelInspector modelInspector, PropertyPath member, ISetPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Cascade(Cascade.All);
propertyCustomizer.Lazy(CollectionLazy.Extra);
propertyCustomizer.Fetch(CollectionFetchMode.Subselect);
}
protected void SetupInspectors()
{
IsRootEntity((type, declared) =>
{
return baseType.Equals(type.BaseType);
});
IsEntity((type, declared) =>
{
return baseType.IsAssignableFrom(type) && !type.IsInterface;
});
IsVersion((member, declared) =>
{
return
member.Name == "Version" &&
member.MemberType == MemberTypes.Property &&
member.GetPropertyOrFieldType() == typeof(int);
});
IsBag((member, declared) =>
{
if (member.GetPropertyOrFieldType().IsGenericType)
return IsGenericType(member, typeof(ICollection<>));
return false;
});
IsList((member, declared) =>
{
if (member.GetPropertyOrFieldType().IsGenericType)
return IsGenericType(member, typeof(IList<>));
return false;
});
IsSet((member, declared) =>
{
if (member.GetPropertyOrFieldType().IsGenericType)
return IsGenericType(member, typeof(ICG.ISet<>));
return false;
});
}
protected static bool IsGenericType(MemberInfo member, Type targetType)
{
var type = member.GetPropertyOrFieldType();
var generics = type.GetGenericInterfaceTypeDefinitions();
return generics.Contains(targetType);
}
}
The problem is probably with your IsEntity convention. Currently, it will return true for Entity class itself. Just add another check:
IsEntity((type, declared) =>
{
return baseType.IsAssignableFrom(type) && !type.IsInterface &&
type != typeof(Entity); // <- skip Entity class
});
Edit
Also, you have two constructors in your CustomModelMapper class. One of them accepts base type, the other one is default and calls SetupInspectors(). As I can see, your default constructor will never be called, since you are calling the one that accepts the base type, and it doesn't call the default constructor...
And the consequence of that is... your SetupInspectors() method will also never be called.
Based on Jimmy's Enumeration classes idea I am wanting to see if I can avoid using the constructor to instantiate my type (which I assume is happening with the discriminator-value) but rather use a "factory method"-esque way of getting my instance mapped from the db.
Here is my type:
public class Impact : Enumeration
{
public static readonly Impact Carbon
= new Impact(1, "Carbon dioxide equivalent", CommonUnit.CO2e);
public static readonly Impact Energy
= new Impact(2, "Energy", CommonUnit.MJ);
public static readonly Impact Cost
= new Impact(3, "Cost", CommonUnit.Dollars);
public Impact(int index, string name, CommonUnit unit)
: base(index, name)
{
this.Unit = unit;
}
public CommonUnit Unit { get; private set; }
}
And here is the definition for Enumeration:
public class Enumeration : ValueObject
{
public Enumeration(int index, string displayName)
{
this.Index = index;
this.DisplayName = displayName;
}
public int Index { get; private set; }
public string DisplayName { get; private set; }
public override string ToString()
{
return this.DisplayName;
}
public static IEnumerable<T> GetAllFor<T>() where T : Enumeration
{
foreach (var publicStatic in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
Enumeration item = null;
item = (Enumeration)publicStatic.GetValue(null);
yield return item as T;
}
}
public static T With<T>(int index) where T : Enumeration
{
return GetAllFor<T>().SingleOrDefault(i => i.Index == index);
}
}
ValueObject simply covers off Equality functionality.
Elsewhere I use the static methods to get items from this enum (kinda like how you could use the core Enumeration static methods):
impact = Impact.With<Impact>(index.ImpactId.Value);
This is pretty handy but I want to know if I can get NHibernate to do this too when rehydrating objects.
Can it be done and how?
With an NHibernate Custom Type:
public class EnumerationType<T> : PrimitiveType where T : Enumeration
{
public EnumerationType()
: base(new SqlType(DbType.Int32))
{
}
public override object Get(IDataReader rs, int index)
{
object o = rs[index];
var value = Convert.ToInt32(o);
return Enumeration.With<T>(value);
}
public override object Get(IDataReader rs, string name)
{
int ordinal = rs.GetOrdinal(name);
return Get(rs, ordinal);
}
public override Type ReturnedClass
{
get { return typeof(T); }
}
public override object FromStringValue(string xml)
{
return int.Parse(xml);
}
public override string Name
{
get { return "Enumeration"; }
}
public override void Set(IDbCommand cmd, object value, int index)
{
var parameter = (IDataParameter)cmd.Parameters[index];
var val = (Enumeration)value;
parameter.Value = val.Value;
}
public override string ObjectToSQLString(object value, Dialect dialect)
{
return value.ToString();
}
public override Type PrimitiveClass
{
get { return typeof(int); }
}
public override object DefaultValue
{
get { return 0; }
}
}
If you're doing an HBM.xml-based mapping, you can set the custom type like this:
<property name="Impact" column="Impact" type="Namespace.To.EnumerationType`1[[Impact, AssemblyWithDomainEnum]], AssemblyWithNHibCustomType"/>
Alternatively, if you're using Fluent NHibernate, you can create a convention to map all enumeration types without having to configure each one individually:
public class EnumerationTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
private static readonly Type _openType = typeof(EnumerationType<>);
public void Apply(IPropertyInstance instance)
{
var closedType = _openType.MakeGenericType(instance.Property.PropertyType);
instance.CustomType(closedType);
}
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => typeof(Enumeration).IsAssignableFrom(x.Property.PropertyType));
}
}
And then add that convention however you like in your Fluent NHibernate configuration.
This seemed to work too, but perhaps Jimmy's way seems easier:
public class ImpactEnumType : IUserType
{
public SqlType[] SqlTypes
{
get
{
//We store our Impact in a single column in the database that can contain a int (for the index value)
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.Int32);
return types;
}
}
public Type ReturnedType
{
get { return typeof(Impact); }
}
public bool Equals(object x, object y)
{
// Impact is derived from ValueObject which implements Equals
return x.Equals(y);
}
public int GetHashCode(object x)
{
// as above
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
//We get the string from the database using the NullSafeGet used to get ints
int impactIndex = (int)NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
// then pull the instance from the Enumeration type using the static helpers
return Impact.With<Impact>(impactIndex);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
//Set the value using the NullSafeSet implementation for int from NHibernateUtil
if (value == null)
{
NHibernateUtil.Int32.NullSafeSet(cmd, null, index);
return;
}
value = (value as Impact).Index;
NHibernateUtil.Int32.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
//We deep copy the Impact by creating a new instance with the same contents
if (value == null) return null;
return Impact.With<Impact>((value as Impact).Index);
}
public bool IsMutable
{
get { return false; }
}
public object Replace(object original, object target, object owner)
{
//As our object is immutable we can just return the original
return original;
}
public object Assemble(object cached, object owner)
{
//Used for casching, as our object is immutable we can just return it as is
return cached;
}
public object Disassemble(object value)
{
//Used for casching, as our object is immutable we can just return it as is
return value;
}
}
My HBM XML:
<property name="Impact" column="ImpactIndex" type="namespace.childnamespace.ImpactEnumType, namespace.childnamespace" />
private ISessionFactory GetSessionFactory(string sessionFactoryConfigPath)
{
GetFullSessionFactoryFor(sessionFactoryConfigPath);
while (!sessionFactoryReady) Thread.Sleep(1000);
return (ISessionFactory)sessionFactories[sessionFactoryConfigPath];
}
private void GetFullSessionFactory(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
ISessionFactory sessionFactory=null;
FluentConfiguration fluentConfiguration = fluentConfiguration.ExposeConfiguration(c => c.SetProperty("sessionfactoryname","somevalue"))
.Mappings(m =>
{
m.FluentMappings
.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None()
)
.Conventions.AddFromAssemblyOf<"SomeConvention">();
}
);
sessionFactory = fluentConfiguration.BuildSessionFactory();
});
}
I am creating minisession factory on main thread(not shown here) and full session factory on second thread.
The problem is when it hits buildsessionfactory the code never returns back.Am i doing it right?
public class NHibernateBaseDAO<T>
{
public NHibernateBaseDAO(string sessionFactoryConfigPath, int sessionId)
{
SessionFactoryConfigPath = sessionFactoryConfigPath;
SessionId = sessionId;
public bool Save(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.Save(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public bool SaveOrUpdate(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.SaveOrUpdate(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public void Delete(T entity)
{
NHibernateSession.Delete(entity);
}
public void CommitChanges()
{
if (NHibernateSessionManager.Instance.HasOpenTransactionOn(SessionFactoryConfigPath, this.SessionId))
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
NHibernateSessionManager.Instance.CommitTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
else
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
}
}
public void BeginTransaction()
{
NHibernateSessionManager.Instance.BeginTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public void RollbackTransaction()
{
NHibernateSessionManager.Instance.RollbackTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public bool IsDirty()
{
return NHibernateSession.IsDirty();
}
public IQueryable<T> Query() {
return (IQueryable<T>)NHibernateSession.Query<T>();
}
protected ISession NHibernateSession
{
get
{
return NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId);
}
}
protected readonly string SessionFactoryConfigPath;
protected int SessionId;
protected System.Data.IDbConnection DbConnection
{
get { return NHibernateSessionManager.Instance.GetDbConnection(SessionFactoryConfigPath, this.SessionId); }
}
/// <summary>
/// Return a list of object arrays. use this for general queries
/// </summary>
public System.Collections.IEnumerable GetSqlQuery(string queryString, IList<Criterion> criterion, Type returnType)
{
queryString += CriteriaToSql(criterion);
return NHibernateSession.CreateQuery(queryString).Enumerable();
}
protected ICriteria AddCriteria(IList<Criterion> criterion)
{
ICriteria criteria = NHibernateSession.CreateCriteria(persistentType);
foreach (Criterion criterium in criterion)
{
switch (criterium.Comparison)
{
case SqlComparison.StartsWith:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Start));
break;
case SqlComparison.Contains:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Anywhere));
break;
case SqlComparison.Equals:
criteria.Add(Restrictions.Eq(criterium.Property, criterium.Value1));
break;
case SqlComparison.Between:
criteria.Add(Restrictions.Between(criterium.Property, criterium.Value1, criterium.Value2));
break;
case SqlComparison.MoreThan:
criteria.Add(Restrictions.Gt(criterium.Property, criterium.Value1));
break;
case SqlComparison.LessThan:
criteria.Add(Restrictions.Lt(criterium.Property, criterium.Value2));
break;
case SqlComparison.InList:
criteria.Add(Restrictions.In(criterium.Property, (System.Collections.IList)criterium.Value1));
break;
}
}
return criteria;
}
protected string CriteriaToSql(IList<Criterion> criterion)
{
}
/// <summary>
/// Get delimiter for data, defaults to ' unless specifed for data type
/// </summary>
protected string[] GetDelimiter(object value)
{
}
public class Criterion
{
public Criterion(string property, SqlComparison comparison, object value1)
{
Property = property;
Comparison = comparison;
Value1 = value1;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
}
public Criterion(string property, SqlComparison comparison, object value1, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Not = not;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
Not = not;
}
public string Property { get; set; }
public bool Not { get; set; }
public SqlComparison Comparison { get; set; }
public object Value1 { get; set; }
public object Value2 { get; set; }
}
public enum SqlComparison { StartsWith, Contains, Equals, Between, MoreThan, LessThan, InList }
}
one last question please. I am using generic class to access sessionfactory so i cannot explicitly access the minisession. with this how do i access minisession based only on certain entities if the full session is not available and full session factory when it is available.
you never set sessionfactoryready to true
Update: a more complete example.
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
var minifactory = Database.GetMiniFactory("<sessionFactoryConfigPath>");
// Do some stuff with minifactory
var realsessionfactory = Database.SessionFactory;
// Do stuff with real factory
}
static class Database
{
private static ISessionFactory sessionFactory;
public void InitRealFactoryAsync(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
sessionFactory = Fluently.Configure()
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
});
}
public ISessionFactory GetMiniFactory(string sessionFactoryConfigPath)
{
var assembly = Assembly.Load("nameofassembly");
return Fluently.Configure()
.Mappings(m => m.FluentMappings.Add(assembly.GetTypes().Where(Filter).ToArray())
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
}
public static ISessionFactory SessionFactory
{
get {
while (sessionFactory == null) Thread.Sleep(1000);
return sessionFactory;
}
}
}
UpdateUpdate:
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
Database.InitMiniFactory("<sessionFactoryConfigPath>");
using (var session = Database.GetSession(true))
{
// Do some stuff where minifactory is enough
}
using (var session = Database.GetSession())
{
// Do stuff with real factory
}
...
}
// class Database
public ISession GetSession()
{
return GetSession(false);
}
public ISession GetSession(bool miniFactoryIsEnough)
{
if (realSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactoryIsEnough)
return miniSessionfactory.OpenSession();
else
{
while (realSessionFactory == null) Thread.Sleep(1000);
return realSessionfactory.OpenSession();
}
}
Update: "access minisession based only on certain entities"
you need to specify the type you want to use in the session:
public ISession GetSession(Type persistentType)
{
if (fullSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactory.GetClassMetadata(persistentType) != null)
return miniSessionfactory.OpenSession();
else
{
// minifactory doesnt contain the type needed, wait for full factory
while (fullSessionFactory == null) Thread.Sleep(1000);
return fullSessionfactory.OpenSession();
}
}
some additional advice
do not catch (NHibernate.HibernateException)
you lose valuable information and the calling code can't really decide what to do when false is returned
session state is inconsistent see https://stackoverflow.com/a/1819150/671619
FlushMode should be Flushmode.Commit and public void CommitChanges() can be written as
var session = NHibernateSession;
if (session.Transaction.IsActiv)
{
session.Transaction.Commit();
}
cut out the whole sessionId stuff as it seems to provide no value. hold the session instead of sessionId instead
I'm trying to implement an IUserType for states and country codes that will allow me to access both the two-letter code (what's stored in the database) as well as the full name. I'm following the example in the NHibernate 3.0 Cookbook (p. 225), but my problem is that my StreetAddress class is currently mapped as a component in my automapping configuration:
public override bool IsComponent(Type type)
{
return type == typeof(StreetAddress);
}
With this class identified as a component, I don't know how I can use an IUserType for the component class's property, since that class isn't explicitly mapped. There's nowhere that I could tell fluent NHibernate to use the IUserType specification.
#Firo was close, but there turned out to be a much easier solution. There were two steps here. First, I had to tell Fluent NHibernate not to map the State and Country classes, which reside in my domain layer:
public override bool ShouldMap(Type type)
{
return type.Name != "State" && type.Name != "Country";
}
Next, I simply had to create the conventions for the IUserType classes. This turned out to be easier than #Firo suggested:
public class CountryUserTypeConvention : UserTypeConvention<CountryType>
{
}
public class StateUserTypeConvention : UserTypeConvention<StateType>
{
}
The definition of those IUserTypes was pulled out of the cookbook referenced in the original question, but in case you don't want to read it:
public class CountryType : GenericWellKnownInstanceType<Country, string>
{
// The StateType is pretty much the same thing, only it uses "StateCode" instead of "CountryCode"
private static readonly SqlType[] sqlTypes =
new[] {SqlTypeFactory.GetString(2)};
public CountryType()
: base(new Countries(),
(entity, id) => entity.CountryCode == id,
entity => entity.CountryCode)
{
}
public override SqlType[] SqlTypes
{
get { return sqlTypes; }
}
}
And that derives from GenericWellKnownInstanceType:
[Serializable]
public abstract class GenericWellKnownInstanceType<T, TId> :
IUserType where T : class
{
private Func<T, TId, bool> findPredicate;
private Func<T, TId> idGetter;
private IEnumerable<T> repository;
protected GenericWellKnownInstanceType(
IEnumerable<T> repository,
Func<T, TId, bool> findPredicate,
Func<T, TId> idGetter)
{
this.repository = repository;
this.findPredicate = findPredicate;
this.idGetter = idGetter;
}
public Type ReturnedType
{
get { return typeof (T); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(null, x) ||
ReferenceEquals(null, y))
{
return false;
}
return x.Equals(y);
}
public int GetHashCode(object x)
{
return (x == null) ? 0 : x.GetHashCode();
}
public object NullSafeGet(IDataReader rs,
string[] names, object owner)
{
int index0 = rs.GetOrdinal(names[0]);
if (rs.IsDBNull(index0))
{
return null;
}
var value = (TId) rs.GetValue(index0);
return repository.FirstOrDefault(x =>
findPredicate(x, value));
}
public void NullSafeSet(IDbCommand cmd,
object value, int index)
{
if (value == null)
{
((IDbDataParameter) cmd.Parameters[index])
.Value = DBNull.Value;
}
else
{
((IDbDataParameter) cmd.Parameters[index])
.Value = idGetter((T) value);
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original,
object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
/// <summary>
/// The SQL types for the columns
/// mapped by this type.
/// </summary>
public abstract SqlType[] SqlTypes { get; }
}
The repositories for these classes are just a pair of ReadOnlyCollection of the State and Country objects. Again, from the cookbook:
public class States : ReadOnlyCollection<State>
{
// Truncated in the interest of brevity
public static State Arizona = new State("AZ", "Arizona");
public static State Florida = new State("FL", "Florida");
public static State California = new State("CA", "California");
public static State Colorado = new State("CO", "Colorado");
public static State Oklahoma = new State("OK", "Oklahoma");
public static State NewMexico = new State("NM", "New Mexico");
public static State Nevada = new State("NV", "Nevada");
public static State Texas = new State("TX", "Texas");
public static State Utah = new State("UT", "Utah");
public States() : base(new State[]
{
Arizona, Florida, California, Colorado,
Oklahoma, NewMexico, Nevada, Texas, Utah
}
)
{
}
}
Hopefully this helps someone out there.
i couldnt test it, but it should be possible using a convention
public class ComponentConvention : IComponentConvention, IComponentConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IComponentInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(StreetAddress);
}
public void Apply(IComponentInstance instance)
{
instance.Properties.First(p => p.Name == "CountrCode").CustomType<MyUserType>();
}
}
I'm quite new to nhibernate, I was doing all right until I face this problem, It looks like a NHibernate bug, but being a newbie with it, it can certainly be my fault
Having this base class to do all the Id and equality stuff
public abstract class ObjetoConId
{
public ObjetoConId()
{
Id=NewId();
}
public virtual Guid Id {get;private set;}
public override bool Equals(object o)
{
if (Object.ReferenceEquals(this,o))
return true;
if (o==null) return false;
ObjetoConId oId;
oId= o as ObjetoConId;
if (!Object.ReferenceEquals(oId,null))
return (Id.Equals(oId.Id));
return (base.Equals(o));
}
public override int GetHashCode()
{
byte[] bId;
bId=Id.ToByteArray();
return ((Int32)(bId[8]^bId[12])<<24) +
((Int32)(bId[9]^bId[13])<<16) +
((Int32)(bId[10]^bId[14])<<8) +
((Int32)(bId[11]^bId[15]));
}
public virtual bool Equals(ObjetoConId o)
{
if (Object.ReferenceEquals(this,o))
return true;
if (Object.ReferenceEquals(o,null)) return false;
return (Id.Equals(o.Id));
}
public virtual string toString()
{
return this.GetType().FullName
+ "[id=" + Id + "]";
}
protected virtual Guid NewId()
{
return GuidComb.NewGuid();
}
public static bool operator == (ObjetoConId x,ObjetoConId y)
{
if(Object.ReferenceEquals(x,y))
return true;
if(Object.ReferenceEquals(x,null))
return false;
return x.Equals(y);
}
public static bool operator != (ObjetoConId x,ObjetoConId y)
{
return !(x==y);
}
/// <summary>
/// Metodo interno para permitir el testing
/// </summary>
/// <param name="id"></param>
internal void setId(Guid id)
{
Id=id;
}
}
and this entity
public class Propiedad : ObjetoConId,IPropiedad
{
[Obsolete("Persistance Constructor only")]
public Propiedad ()
{
}
public Propiedad (IList<IDescripcionCalificada> descripciones)
{
Descripciones=new Dictionary<string,IDescripcionCalificada>(descripciones.Count);
foreach(IDescripcionCalificada d in descripciones)
Descripciones.Add(d.Nombre,d);
}
#region IPropiedad implementation
public virtual IDictionary<string, IDescripcionCalificada> Descripciones {get;private set;}
#endregion
}
and this mapping
public class MapeoPropiedad : ClassMap<Propiedad>
{
public MapeoPropiedad()
{
Id(x => x.Id).Column("pro_id").GeneratedBy.Assigned();
HasMany<DescripcionCalificada>(x => x.Descripciones)
.Cascade.SaveUpdate()
.AsMap<string>(index => index.Nombre)
;
}
}
The test for it is
[TestFixture]
public class TestPropiedadPersistencia
{
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
string connectionString="Server=127.0.0.1;Database=Ana;User ID=dev-test;Password=dev-test;";
fcfg=Fluently.Configure()
.Database(PostgreSQLConfiguration.PostgreSQL82.ConnectionString(connectionString))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MapeoPropiedad>());
fcfg.ExposeConfiguration(cfg => new SchemaExport(cfg).Create(false, true));
sessions=fcfg.BuildSessionFactory();
}
ISessionFactory sessions;
FluentConfiguration fcfg;
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
fcfg.ExposeConfiguration(cfg => new SchemaExport(cfg).Drop(false, true));
sessions.Close();
sessions = null;
fcfg = null;
}
[Test]
public void CanCorrectlyMapPropiedad()
{
DescripcionCalificada descri1=new DescripcionCalificada("descri",new Descripcion("Esta es la descri"));
DescripcionCalificada descri2=new DescripcionCalificada("descriLarga",new Descripcion("Esta es la descriLarga"));
Dictionary<string,IDescripcionCalificada> descris=new Dictionary<string, IDescripcionCalificada>(2);
descris.Add(descri1.Nombre,descri1);
descris.Add(descri2.Nombre,descri2);
new PersistenceSpecification<Propiedad>(sessions.OpenSession(),new CustomEqualityComparer() )
.CheckProperty(c => c.Descripciones,descris)
.VerifyTheMappings();
}
}
The thing is that the test fails unless I put Not.LazyLoad() in the mapping
It gives a mapping error
Ana.Nucleo.Lenguaje.Test.TestDescripcionCalificadaPersistencia (TestFixtureSetUp):
FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
----> NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Ana.Catalogacion.Implementacion.Propiedad: method setId should be 'public/protected virtual' or 'protected internal virtual'
without lazy loading it pass, and if I put the Id property in the Propiedad class and not inherit from ObjetoConID it also pass, with and without the Not.LazyLoad().
Anyone can confirm this is a NH bug, or any help will be appreciated
EDIT:
I've found the problem, my fault. I missed the setId internal function not being virtual protected and confused with the setter of the Id property, and thus missunderstood the execption
Fer
I've found the problem, my fault. I missed the setId internal function not being virtual protected and confused with the setter of the Id property, and thus missunderstood the execption