Nhibernate many-to-many with extra column, - nhibernate

I am little bit new to NHibernate. I wish to implement a web application using asp.net using C#.
I have following database schemas:
Database Schemas
Here is my NHibernate Mapping file. I am not sure whether my mapping is correct or not. Please correct me if I made it wrong.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.Status, TelDir.Core" table="tblStatus" lazy="false">
<id name="ID" column="StatusID" unsaved-value="0">
<generator class="identity" />
</id>
<property name="StatusCode" column="StatusCode" />
<property name="StatusName" column="StatusName" />
<!--
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true">
<key column="StatusID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
-->
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.WorkOrder, TelDir.Core" table="tblWorkOrder" lazy="false">
<id name="ID" column="WOID" unsaved-value="0">
<generator class="identity" />
</id>
<property name="WorkOrderRef" column="WORef" />
<property name="WorkOrderDesc" column="WODesc" />
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true">
<key column="WOID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" table="tblWorkOrderStatus" lazy="false">
<composite-id>
<key-many-to-one name="WorkOrder" column="WOID"/>
<key-many-to-one name="Status" column="StatusID"/>
</composite-id>
<property name="LastModifyDateTime" column="LastModifiedOn" type="Timestamp" />
<property name="CreatedBy" column="CreatedBy" />
</class>
</hibernate-mapping>
And my POCO class are presented as below
public class Status : DomainObject<Int16>
{
private string _statuscode = "";
private string _statusname = "";
//private ISet<WorkOrderStatus> _workorder_status = new HashedSet<WorkOrderStatus>() ;
public Status() { }
public Status(string statusCode, string statusName) {
this._statuscode = statusCode;
this._statusname = statusName;
}
public string StatusCode {
get { return _statuscode ; }
set { _statuscode = value; }
}
public string StatusName
{
get { return _statusname; }
set { _statusname = value; }
}
/*
public ISet<WorkOrderStatus> WorkOrderStatus
{
get { return (_workorder_status); }
protected set { _workorder_status = value; }
}
*/
}
public class WorkOrder : DomainObject<long>
{
private string _workorder_ref = "";
private string _workorder_desc = "";
private ISet<WorkOrderStatus> _workorder_status = new HashedSet<WorkOrderStatus>();
public WorkOrder() { }
public WorkOrder(string wref, string wdecs) {
this._workorder_ref = wref;
this._workorder_desc = wdecs;
}
public string WorkOrderRef {
get { return _workorder_ref ; }
set { _workorder_ref = value; }
}
public string WorkOrderDesc
{
get { return _workorder_desc; }
set { _workorder_desc = value; }
}
public ISet<WorkOrderStatus> WorkOrderStatus
{
get { return (_workorder_status); }
protected set { _workorder_status = value; }
}
public void AddStatus(Status st, DateTime dt)
{
WorkOrderStatus obj = new WorkOrderStatus();
obj.WorkOrder = this;
obj.Status = st;
obj.LastModifyDateTime = dt;
_workorder_status.Add(obj);
}
}
public class WorkOrderStatus
{
private DateTime _lastmodifydt;
private WorkOrder _workorder;
private Status _status;
private int _createdby;
public WorkOrderStatus() {
}
public DateTime LastModifyDateTime{
get { return _lastmodifydt; }
set { _lastmodifydt = value; }
}
public WorkOrder WorkOrder
{
get { return _workorder; }
set { _workorder = value; }
}
public Status Status
{
get { return _status; }
set { _status = value; }
}
public int CreatedBy {
get { return _createdby; }
set { _createdby = value; }
}
public override bool Equals(object other)
{
//if (this == other) return true;
//WorkOrderStatus obj = other as WorkOrderStatus;
//if (obj == null) return false; // null or not a cat
//if (_lastmodifydt != obj._lastmodifydt ) return false;
//return true;
if (other == null)
return false;
WorkOrderStatus t = other as WorkOrderStatus;
if (t == null)
return false;
if (WorkOrder == t.WorkOrder && Status == t.Status && _lastmodifydt == t.LastModifyDateTime )
return true;
return false;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = _lastmodifydt.GetHashCode();
result = 29 * result + WorkOrder.GetHashCode() + Status.GetHashCode();
return result;
}
//return (WorkOrder.ID + "|" + Status.ID + "|" + Status.StatusName).GetHashCode();
}
}
I want my data present in tables like this:
[tblWorkOrderStatus]
StatusID WOID LastModifiedOn CreatedBy
--------------------------------------------------------------------------
2 1 06/20/2012 09:45:40.209 1
[tblWorkOrder]
WOID WORef WODesc
-------------------------------------------
1 001 Test-001
[tblStatus]
StatusID StatusCode StatusName
-----------------------------------------------
1 'X001' OPEN
2 'X002' CLOSE
What should I do to add record to [tblWorkOrderStatus]?
I have written test code as following but I found no record add in association table [tblWorkOrderStatus], I dont know why it does not added.
WorkOrder Wo = new WorkOrder('001', 'Test-001');
daoFactory.GetWorkOrderDao().Save(Wo);
Status St = daoFactory.GetStatusDao().GetById(1, false);
//// Secode Methode
WorkOrderStatus _objWS = new WorkOrderStatus();
_objWS.WorkOrder = Wo;
_objWS.Status = St;
_objWS.LastModifyDateTime = DateTime.Now;
_objWS.CreatedBy = 1; //suppose 1 is current login UserID
Wo.WorkOrderStatus.Add(_objWS);
daoFactory.GetWorkOrderDao().Save(Wo);
I might missing something in POCO, NHibernate mapping file, or somewhere else. Could you please guide me to the right solution?
Best regards,
Here is my stacktrace :
" at System.ThrowHelper.ThrowKeyNotFoundException()\r\n
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)\r\n
at NHibernate.Engine.StatefulPersistenceContext.RemoveEntity(EntityKey key)\r\n
at NHibernate.Action.EntityDeleteAction.Execute()\r\n
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)\r\n
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)\r\n
at NHibernate.Engine.ActionQueue.ExecuteActions()\r\n
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)\r\n
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)\r\n
at NHibernate.Impl.SessionImpl.Flush()\r\n
at NHibernate.Transaction.AdoTransaction.Commit()\r\n
at TelDir.Data.NHibernateSessionManager.CommitTransaction()
in E:\\OLD PC\\D\\WORKS\\PROJECT\\TelDIR\\Data\\NHibernateSessionManager.cs:line 120\r\n
at TelDir.Web.NHibernateSessionModule.CommitAndCloseSession(Object sender, EventArgs e)
in e:\\OLD PC\\D\\WORKS\\PROJECT\\TelDIR\\Web\\App_Code\\NHibernateSessionModule.cs:line 38\r\n
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()\r\n
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)"

The main issue is that there is no cascade on WorkOrder.WorkOrderStatus, so NHibernate will not know to persist changes it finds in that collection when you save.
I changed WorkOrder.hbm.xml so the set looks like this:
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true" cascade="all-delete-orphan">
<key column="StatusID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
And then this test passed:
// Arrange
var workorder = new WorkOrder("001", "Test-001");
var status = new Status("1", "Status-1");
workorder.AddStatus(status, DateTime.Now);
WorkOrderStatus expected;
// Act
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
session.Save(status);
session.SaveOrUpdate(workorder);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
}
// Assert
expected.Should().NotBeNull();
expected.Status.Should().Be(status);
expected.WorkOrder.Should().Be(workorder);
Removing
Uncomment the ISet<WorkOrderStatus> WorkOrderStatus property on Status. Also, uncomment <set name="WorkOrderStatus" ... in Status.hbm.xml, and add the attribute cascade="all-delete-orphan" like you did on WorkOrder.
Add to WorkOrder:
public void RemoveStatus(WorkOrderStatus item)
{
if (!WorkOrderStatus.Contains(item)) return;
item.Status.WorkOrderStatus.Remove(item);
WorkOrderStatus.Remove(item);
}
Now, this test should pass:
// Arrange
var workorder = new WorkOrder("001", "Test-001");
var status = new Status("1", "Status-1");
workorder.AddStatus(status, DateTime.Now);
WorkOrderStatus expected;
// Act
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
session.Save(status);
session.SaveOrUpdate(workorder);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
expected.WorkOrder.RemoveStatus(expected);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
}
// Assert
expected.Should().BeNull();

Related

Log4net, eliminate duplicate messages

is there any option to setup log4net to eliminate duplicate messages? I have an application which works in cycles...there is an infinate while cycle. If any error occure (for example database is not accesible), same message is logged again and again in every loop. I need to log the message just once.
I have implemented log4net filter to eliminate same messages during specified time interval.
The class:
public class DuplicityFilter : FilterSkeleton
{
private String lastMessage = null;
private List<Tuple<DateTime, String>> lastMessages = new List<Tuple<DateTime,string>>();
private Int32 _timeWindow = 0;
public Int32 timeWindow
{
get { return _timeWindow; }
set { _timeWindow = value; }
}
public Boolean _lastOnly = false;
public Boolean lastOnly
{
get { return _lastOnly; }
set { _lastOnly = value; }
}
public override FilterDecision Decide(log4net.Core.LoggingEvent loggingEvent)
{
if (_lastOnly)
{
if (lastMessage == loggingEvent.RenderedMessage)
{
return FilterDecision.Deny;
}
else
{
lastMessage = loggingEvent.RenderedMessage;
return FilterDecision.Accept;
}
}
else
{
if (_timeWindow <= 0)
return FilterDecision.Accept;
// Removes old messages
lastMessages.RemoveAll(m => m.Item1 < DateTime.Now.AddSeconds(0 - _timeWindow));
if (!lastMessages.Any(m => m.Item2 == loggingEvent.RenderedMessage))
{
lastMessages.Add(new Tuple<DateTime, string>(loggingEvent.TimeStamp, loggingEvent.RenderedMessage));
return FilterDecision.Accept;
}
else
{
return FilterDecision.Deny;
}
}
}
And the XML definition:
<filter type="your.namespace.here.DuplicityFilter">
<timeWindow value="900" /> <!-- 15min -->
<lastOnly value="false" />
</filter>

NHibernate New item added to bag collection does not get saved when updating parent entity

I'm using NHibernate version 3.1.0.4000 with hbm mapping files.
When I have an entity which contains a bag collection and i save the entity with all the items added to the bag then it saves all the collection items fine. However, when i then subsequently (after the initial save) add another item to the collection and save the entity then it does not save the new item in the collection. I had a look at the SQL being generated by NHibernate and it creates the initial Insert SQL statement, but it does not create the Update SQL statement to update the foreign key value. This issue occurs for all bags in the solution.
Here is a mapping extract:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-lazy="false"
namespace="some_namespace"
assembly="some_assembly">
<class name="Landing"
table="[Landing]"
select-before-update="true"
optimistic-lock="version">
<id name="Id"
column="[Id]"
unsaved-value="null">
<generator class="assigned" />
</id>
<version name="Version"
column="[Version]"
unsaved-value="null" />
<bag name="LandingPermits"
cascade="all-delete-orphan"
access="field.camelcase">
<key column="[LandingId]" />
<one-to-many class="LandingPermit" />
</bag>
</class>
</hibernate-mapping>
Here's the Save method on my NHibernate Repository:
public NHibernateRepository(ISessionFactory factory, string queriesAssemblyName = null, string clientName = null)
{
this.sessionFactory = factory;
this.queriesAssemblyName = queriesAssemblyName;
this.clientName = clientName;
if (!string.IsNullOrEmpty(this.queriesAssemblyName))
LoadQueries();
}
public virtual void Save(IAggregateRoot entity)
{
Save(new IAggregateRoot[] { entity });
}
public virtual void Save(IAggregateRoot[] entities)
{
try
{
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
foreach (var entity in entities)
{
if (entity.IsNew)
entity.AddedDateTime = DateTime.Now;
else
entity.UpdatedDateTime = DateTime.Now;
session.SaveOrUpdate(entity.GetType().Name, entity);
}
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
throw e;
}
finally
{
if (session.Connection.State == ConnectionState.Open)
session.Connection.Close();
session.Connection.Dispose();
session.Close();
}
}
}
}
catch (GenericADOException e)
{
var sqlException = e.InnerException as SqlException;
if ((sqlException != null) && (sqlException.Number == ForeignKeyExceptionNumber))
throw new EntityInUseException("Save", sqlException);
else
throw new RepositoryException("Save", e.InnerException);
}
catch (StaleObjectStateException e)
{
throw new ConcurrencyException("Save", e, new Identity((Guid?)e.Identifier));
}
catch (Exception e)
{
throw new RepositoryException("Save", e);
}
}
I have tried a few things, including setting the inverse property to true, but no success.
Hope this is enough information for anyone to asssist.
Thanks
If your collection is inverse=false (the default), that means that the owner of the collection (the parent) is responsible for the relationship.
It's not clear in your code above if both parent and child are IAggregateRoot, but if they both are it means that, when you save the child in a session not aware of the parent, that the child will be saved fine but will not be added to the collection.
If you would use inverse=true, the child would be responsible for the relationship. In this case the model should be bidirectional and you should map both ends.
You have a few options here...
Is the child really an aggregate root? Maybe it should be persisted together with its parent?
Use a bidirectional relationship and use inverse=true

Nhibernate QueryOver by Enum Flags

I have a query by QueryOver :
public IList<Person> SearchTest(PersonEnumType type)
{
var q = SessionInstance.QueryOver<Person>();
q = q.Where(x => (x.PersonEnumType & type) == type);
return q.List<Person>();
}
and PersonEnumType is a Enum flags :
[Flags]
public enum PersonEnumType
{
Employee1 = 1,
Employee2 = 2,
Employee3 = 4
}
This throws Could not determine member from (Convert(x.PersonEnumType) & Convert(value(NHibernate.Repository.PersonRepositoryNh+<>c__DisplayClass2).type))
Of course this works in Nhibernate.Linq.
Why?
if you've mapped your property properly in your mapping file:
<property name="PersonEnumType" type="MyApp.PersonEnumType, MyApp">
<column name="Person" default="1" />
</property>
You can achieve what you're looking for using filters.
I don't know if this is the only solution but, here it goes:
You can create a filter definition:
<filter-def name="PersonEnumTypeFilter">
<filter-param name="personType" type="MyApp.PersonEnumType, MyApp"/>
</filter-def>
and implement it in your class mapping:
<filter name="PersonEnumTypeFilter" condition="(:personType & PersonEnumType) = PersonEnumType"/>
Now you can switch on your filter:
public IList<Person> SearchTest(PersonEnumType type)
{
SessionInstance.EnableFilter("PersonEnumTypeFilter").SetParameter("personType", type);
var q = SessionInstance.Query<Person>();
return q.ToList<Person>();
}
You can read more about filters here.

Cant query sybase with Nhibernate

Have started to use NHibernate on sybase ASE data, problem am facing is when I load entity I get below error
"System.IndexOutOfRangeException : Invalid index 0 for this OdbcParameterCollection with Count=0."
This is how I configure session
properties["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
properties["connection.driver_class"] = "NHibernate.Driver.OdbcDriver";
properties["connection.connection_string"] = #"Driver={Adaptive Server Enterprise};server=;port=; db=;uid=;pwd=";
properties["dialect"] = "NHibernate.Dialect.SybaseASE15Dialect";
And object mapping
<class name="MenuGroup" table="MENU_GROUP">
<id name="Id" column="id" type="Int32">
<generator class="identity" />
</id>
<property name="Name" column="name" type="String" length="100" not-null="true" />
<property name="Position" column="position" type="Int32" />
</class>
and If I do
var menuGroup = _session.Get<Menu.MenuGroup>(1);
I get error
NHibernate.Exceptions.GenericADOException : could not load an entity: [DomainModel.Menu.MenuGroup#1][SQL: SELECT menugroup0_.id as id1_0_, menugroup0_.name as name1_0_, menugroup0_.position as position1_0_ FROM MENU_GROUP menugroup0_ WHERE menugroup0_.id=?]
----> System.IndexOutOfRangeException : Invalid index 0 for this OdbcParameterCollection with Count=0.
I solved this problem by creating my own connection driver
using NHibernate.Driver;
namespace Framework.Persistency
{
public sealed class MySybaseSQLAnywhereDriver : SybaseSQLAnywhereDriver
{
public override bool UseNamedPrefixInSql
{
//default is false
get { return true; }
}
public override bool UseNamedPrefixInParameter
{
//default is false
get { return true; }
}
public override string NamedPrefix
{
//default is string.Empty
get { return ":"; }
}
}
}
And use it in the NHibernate config:
configDictionary.Add(Environment.ConnectionDriver, typeof(MySybaseSQLAnywhereDriver).AssemblyQualifiedName);
moving away from odbc helped, changed the config to
properties["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
properties["connection.driver_class"] = "NHibernate.Driver.SybaseAseClientDriver";
properties["connection.connection_string"] = #"server=*;port=5000; db=;user id=*;password=;";
properties["dialect"] = "NHibernate.Dialect.SybaseASE15Dialect";

NHibernate, joined subclass hierarchy, PreUpdate event data changes on an entity which is only modified in the PreUpdate event is not persisted

Overview: With NHibernate I am experimenting with a 3 layered hierarchy using joined subclasses. There is a Category, which inherits from AuditableEntity (to add PreUpdate and PreInsert audit trail), which finally inherits from an Entity.
Problem: None of the data changes to the AuditableEntity object, which are carried out exactly as Ayende’s blog post, are being persisted to the database. The AuditableEntity objects properties are successfully updated by the PreUpdate code, but it is as if NHibernate is not seeing the AuditableEntity as dirty as no update sql statement occurs.
Hbm:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Learning"
namespace="Learning.entities">
<class name="Entity" >
<id name="Id" type="guid">
<generator class="guid.comb"></generator>
</id>
<version name="Version"/>
<joined-subclass name="AuditableEntity" >
<key column="AuditableEntity_id"></key>
<property name="CreatedOn" ></property>
<property name="CreatedBy" ></property>
<property name="LastModifiedOn" ></property>
<property name="LastModifiedBy" ></property>
<joined-subclass name="Category">
<key column="AuditableEntity_id"></key>
<property name="Name" />
</joined-subclass>
</joined-subclass>
</class>
</hibernate-mapping>
NHibernate config for listeners:
<event type="pre-insert">
<listener class="Learning.eventlisteners.AuditInsertEventListener, Learning" />
</event>
<event type="pre-update">
<listener class="Learning.eventlisteners.AuditUpdateEventListener, Learning" />
</event>
PreUpdate code:
namespace Learning.eventlisteners
{
public class AuditInsertEventListener : IPreInsertEventListener
{
public bool OnPreInsert(PreInsertEvent #event)
{
var audit = #event.Entity as IAuditable;
if (audit == null)
return false;
var createdOn = DateTime.Now;
var createdBy = loggedOnProfile;
AuditCommon.Set(#event.Persister, #event.State, "CreatedOn", createdOn);
AuditCommon.Set(#event.Persister, #event.State, "CreatedBy", createdBy);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedOn", createdOn);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedBy", createdBy);
audit.CreatedOn = createdOn;
audit.CreatedBy = createdBy;
audit.LastModifiedOn = createdOn;
audit.LastModifiedBy = createdBy;
return false;
}
}
public static class AuditCommon
{
internal static void Set(IEntityPersister persister, IList<object> state, string propertyName, object value)
{
var index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
}
public class AuditUpdateEventListener : IPreUpdateEventListener
{
public bool OnPreUpdate(PreUpdateEvent #event)
{
var audit = #event.Entity as IAuditable;
if (audit == null)
return false;
var lastModifiedOn = DateTime.Now.AddSeconds(28);
var lastModifiedBy = loggedOnProfile;
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedOn", lastModifiedOn);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedBy", lastModifiedBy);
audit.LastModifiedOn = lastModifiedOn;
audit.LastModifiedBy = lastModifiedBy;
return false;
}
}
}
Code:
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var category = session.Query<Category>().First();
category.Name = "Updated";
session.SaveOrUpdate(category);
transaction.Commit();
}
An observation: if I manually update just one of the AuditableEntity properties before calling SaveOrUpdate, the PreUpdate event is obviously fired and appropriate data changes are made, and then the AuditableEntity data IS persisted to the database.
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var category = session.Query<Category>().First();
category.Name = "Updated";
category.CreatedOn = DateTime.Now;
session.SaveOrUpdate(category);
transaction.Commit();
}
Help: I obviously don't want to have to dummy edit an AuditableEntity properties, so any ideas as to what I am doing wrong here?
To answer this I have authored an nhibernate.info WIKI article - http://nhibernate.info/doc/howto/various/changing-values-in-nhibernate-events
The abstract; Audit trails using NHibernate's event model often use the OnPreInsert and OnPreUpdate event listeners to change/ modify the state of the entity. While this does works and is widely documented as a solution, it should be noted the OnPreInsert and OnPreUpdate events are not intended to be used to change the values of the entity and instead they should be used to check values and for that reason they return "veto".
An update blog post from Fabio - http://fabiomaulo.blogspot.com/2011/05/nhibernate-bizarre-audit.html