I have been searching for several hours now how to do this, but can't seem to find anything to help me.
Here is the database model:
This is the SQL query I am trying to run:
SELECT b.*, a.Assignments FROM Branch b LEFT JOIN (
SELECT b.BranchID , COUNT(ab.BranchID) AS Assignments
FROM Branch b LEFT JOIN AssignmentBranch ab ON b.BranchID = ab.BranchID
GROUP BY b.BranchID
) a ON b.BranchID = a.BranchID
So, basically, I want to return a list of branches and a new column that represents the number of assignments for that branch.
Branch model
public class Branch : IEntity<int>
{
public virtual int ID
{
get;
set;
}
public virtual string Name { get; set; }
public virtual IList<AssignmentBranch> Assignments { get; set; }
}
AssignmentBranch model
public class AssignmentBranch : IEntity<int>
{
public virtual int ID
{
get;
set;
}
public virtual DateTime AssignedOn { get; set; }
public virtual Branch Branch { get; set; }
}
Here is my NHibernate configuration:
<class name="Branch" table="Branch">
<id name="ID" column="BranchID">
<generator class="identity"></generator>
</id>
<property name="Name"/>
<bag name="Assignments" cascade="none" inverse="true">
<key column="BranchID"/>
<one-to-many class="AssignmentBranch"/>
</bag>
<class name="AssignmentBranch" table="AssignmentBranch">
<id name="ID" column="AssignmentBranchID">
<generator class="identity"></generator>
</id>
<property name="AssignedOn" />
<property name="FromDate" />
<property name="ToDate" />
<many-to-one name="Assignment" column="AssignmentID" />
<many-to-one name="Branch" column="BranchID" />
I have tried this a number of ways, but I can't seem to find a way to join with a sub-query using QueryOver.
I tried like this:
// aliases
Branch branch = null; AssignmentBranch assignment = null;
var subquery = QueryOver.Of<Branch>(() => branch)
.Where(() => branch.Project.ID == projectID)
.JoinQueryOver<AssignmentBranch>(() => branch.Assignments, ()=> assignment,
NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.SelectList(list => list
.SelectGroup(x=>x.ID)
.SelectCount(()=>assignment.ID)
);
var query = session.QueryOver<Branch>(()=>branch)
.JoinAlias(???) // how can I join with a sub-query?
.TransformUsing(Transformers.AliasToBean<BranchAssignments>())
.List<BranchAssignments>();
Can anyone help me please? It doesn't have to be with a sub-join exactly, maybe there is another better solution out there that I am missing...
Thank you,
Cosmin
After reading hundreds of similar questions in here, I have found the answer: a correlated sub-query. Like this:
// aliases
Branch branch = null; AssignmentBranch assignment = null;
var subquery = QueryOver.Of<AssignmentBranch>(() => assignment)
.Where(() => assignment.Branch.ID == branch.ID)
.ToRowCountQuery();
var query = session.QueryOver<Branch>(() => branch)
.Where(() => branch.Project.ID == projectID)
.SelectList
(
list => list
.Select(b => b.ID)
.Select(b => b.Name)
.SelectSubQuery(subquery)
)
.TransformUsing(Transformers.AliasToBean<BranchAssignments>())
.List<BranchAssignments>();
The similar question I got my answer from is this one.
its not that easy with QueryOver, because it is currently not possible to have statements in the FROM clause. One thing that comes to my mind (not the most efficient way i think)
var branches = session.QueryOver<Branch>().Future();
var assignmentMap = session.QueryOver<BranchAssignment>()
.Select(
Projections.Group<BranchAssignment>(ab => ab.Branch.Id).As("UserId"),
Projections.RowCount())
.Future<object[]>()
.ToDictionary(o => (int)o[0], o => (int)o[1]);
return branches.Select(b => new { Branch = branch, AssignmentCount = assignmentMap[branch.Id] });
with LINQ it would be
var branchesWithAssignementCount = session.Query<Branch>()
.Select(b => new { Branch = b, AssignmentCount = b.Branch.Count })
.ToList();
Related
I use DB first approach
I have 3 tables with "Order" column (client order of records). I want to create similar logic to reorder in my .NET application, so, I want to work with the base class. I have created the following structure:
but project can't be compiled, column Order is not mapped for each 3 tables and I don't see a way to map it:
How to say, that Order column should be mapped to property Order of parent class?
UPDATED:
Otherwise I have to create similar methods like:
public interface IOrder
{
int Order { get; set; }
}
public partial class EscortDescription : IOrder
{
}
public partial class EscortGroup : IOrder
{
}
public partial class EscortItem : IOrder
{
}
private async Task ReorderEscortAsync(Infrastructure.IOrder item1, Infrastructure.IOrder item2)
{
Random rnd = new Random();
if (item1 == null)
throw new ArgumentNullException("firstItem");
if (item2 == null)
throw new ArgumentNullException("secondItem");
int tmpItem1Order = item1.Order;
int tmpItem2Order = item2.Order;
item1.Order = rnd.Next(int.MinValue, -1);
item2.Order = rnd.Next(int.MinValue, -1);
_db.SaveChanges();
item1.Order = tmpItem2Order;
item2.Order = tmpItem1Order;
await _db.SaveChangesAsync();
}
public async Task EscortGroupItemUpAsync(int ItemID)
{
var firstItem = (from i in _db.EscortItems where i.ID == ItemID select i).FirstOrDefault();
if (firstItem == null)
throw new Domain.RecordNotFoundException<int>(ItemID, "ID", "EscortItems");
var secondItem = (from i in _db.EscortItems where i.Order < firstItem.Order orderby i.Order descending select i).FirstOrDefault();
if (secondItem != null)
await ReorderEscortAsync(firstItem, secondItem);
else
throw new FirstRecordException();
}
public async Task EscortGroupItemDownAsync(int ItemID)
{
var secondItem = (from i in _db.EscortItems where i.ID == ItemID select i).FirstOrDefault();
if (secondItem == null)
throw new Domain.RecordNotFoundException<int>(ItemID, "ID", "EscortItems");
var firstItem = (from i in _db.EscortItems where i.Order > secondItem.Order orderby i.Order ascending select i).FirstOrDefault();
if (firstItem != null)
await ReorderEscortAsync(firstItem, secondItem);
else
throw new LastRecordException();
}
public async Task EscortGroupUpAsync(int ItemID)
{
var firstItem = (from i in _db.EscortGroups where i.ID == ItemID select i).FirstOrDefault();
if (firstItem == null)
throw new Domain.RecordNotFoundException<int>(ItemID, "ID", "EscortGroups");
var secondItem = (from i in _db.EscortGroups where i.Order < firstItem.Order orderby i.Order descending select i).FirstOrDefault();
if (secondItem != null)
await ReorderEscortAsync(firstItem, secondItem);
else
throw new FirstRecordException();
}
public async Task EscortGroupDownAsync(int ItemID)
{
var secondItem = (from i in _db.EscortGroups where i.ID == ItemID select i).FirstOrDefault();
if (secondItem == null)
throw new Domain.RecordNotFoundException<int>(ItemID, "ID", "EscortGroups");
var firstItem = (from i in _db.EscortGroups where i.Order > secondItem.Order orderby i.Order ascending select i).FirstOrDefault();
if (firstItem != null)
await ReorderEscortAsync(firstItem, secondItem);
else
throw new LastRecordException();
}
I would like to have one method and pass objects of base class as parameters
If I understand you well, there is no table for OrderBase. This means that this is a Table-per-Concrete-Type (TPC) mapping. This link shows that with code-first, it's not hard to map TPC.
Database-first and TPC, however, don't play nice together. You have to edit the EDMX to get this done.
I did it with two of your classes. Currently, in the CS mapping content section of your EDMX you'll find something like this:
<EntitySetMapping Name="OrderBases">
<EntityTypeMapping TypeName="IsTypeOf(TPCModel.EcortGroup)">
<MappingFragment StoreEntitySet="EcortGroup">
<ScalarProperty Name="GroupName" ColumnName="GroupName" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(TPCModel.EscortItem)">
<MappingFragment StoreEntitySet="EscortItem">
<ScalarProperty Name="Escort" ColumnName="Escort" />
<ScalarProperty Name="EcortGroup_ID" ColumnName="EcortGroup_ID" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
This tells which columns are mapped to which properties. As you see, Order is missing. But you can add them manually, and EF will be happy and the EDMX can still be opened in the designer:
<EntityTypeMapping TypeName="IsTypeOf(TPCModel.EcortGroup)">
<MappingFragment StoreEntitySet="EcortGroup">
<ScalarProperty Name="GroupName" ColumnName="GroupName" />
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="Order" ColumnName="Order" />
</MappingFragment>
</EntityTypeMapping>
<EntityTypeMapping TypeName="IsTypeOf(TPCModel.EscortItem)">
<MappingFragment StoreEntitySet="EscortItem">
<ScalarProperty Name="Escort" ColumnName="Escort" />
<ScalarProperty Name="EcortGroup_ID" ColumnName="EcortGroup_ID" />
<ScalarProperty Name="ID" ColumnName="ID" />
<ScalarProperty Name="Order" ColumnName="Order" />
</MappingFragment>
</EntityTypeMapping>
HOWEVER: when you update the model from the database, the modifications will be gone. You'll have to add them again each time you do this. If you can, I'd strongly recommend you to port the code to code-first.
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
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();
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.
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