Contents tagged with NHibernate

  • ASP.NET NHibernateDataSource

    A long, long time ago, I wrote a NHibernateDataSource control. Back then, it was based in the first LINQ provider for NHibernate, and a long has happened since. Now, I decided to give it another go! Smile

    Historically, in ASP.NET, a data control should inherit from DataSourceControl, like ObjectDataSource, LinqDataSource, EntityDataSource, SqlDataSource, etc, and should expose collections for parameters for each of the supported operations (select, update, delete and insert). Since ASP.NET 4, however, a new base class came along: QueryableDataSource. This class is an implementation of IQueryableDataSource, which allows using QueryExtender, also introduced in version 4, to filter and sort the results of a data source that uses LINQ.

    I wanted my control to be able to use QueryExtender, but I also wanted to be able to give it an HQL query. It should also be capable of inserting, updating and deleting entities.

    So, here’s what I came up with, first, the NHibernateDataSource class:

       1: [ParseChildren(true)]
       2: public class NHibernateDataSource : QueryableDataSource
       3: {
       4:     public NHibernateDataSource()
       5:     {
       6:         this.SelectParameters = new ParameterCollection();
       7:         this.InsertParameters = new ParameterCollection();
       8:         this.UpdateParameters = new ParameterCollection();
       9:         this.DeleteParameters = new ParameterCollection();
      10:     }
      11:  
      12:     [Description("Raised when a session factory is built")]
      13:     public event EventHandler<BuildSessionFactoryEventArgs> BuildSessionFactory;
      14:     [Description("Raised when a configuration instance is created")]
      15:     public event EventHandler<ConfigureEventArgs> Configure;
      16:     [Description("Raised when an entity is created for inserts or updates")]
      17:     public event EventHandler<CreateInstanceEventArgs> CreateInstance;
      18:  
      19:     [Description("Raised after an entity is inserted")]
      20:     public event EventHandler<EntityEventArgs> EntityInserted;
      21:     [Description("Raised after an entity is deleted")]
      22:     public event EventHandler<EntityEventArgs> EntityDeleted;
      23:     [Description("Raised after an entity is updated")]
      24:     public event EventHandler<EntityEventArgs> EntityUpdated;
      25:     [Description("Raised after a query is executed")]
      26:     public event EventHandler<EntitiesSelectedEventArgs> EntitiesSelected;
      27:     [Description("Raised when an operation completes (select, insert, update or delete)")]
      28:     public event EventHandler<OperationCompletedEventArgs> OperationCompleted;
      29:  
      30:     [Description("Raised before a select is made")]
      31:     public event EventHandler<EntitiesSelectingEventArgs> EntitiesSelecting;
      32:     [Description("Raised before an entity is inserted")]
      33:     public event EventHandler<EntityEventArgs> EntityInserting;
      34:     [Description("Raised before an entity is deleted")]
      35:     public event EventHandler<EntityEventArgs> EntityDeleting;
      36:     [Description("Raised before an entity is updated")]
      37:     public event EventHandler<EntityEventArgs> EntityUpdating;
      38:  
      39:     [Description("The entity name to update, delete or insert")]
      40:     [DefaultValue("")]
      41:     public String EntityName
      42:     {
      43:         get;
      44:         set;
      45:     }
      46:  
      47:     [Description("The HQL to use for selecting records, when mode Hql is selected")]
      48:     [DefaultValue("")]
      49:     public String Hql
      50:     {
      51:         get;
      52:         set;
      53:     }
      54:  
      55:     [Description("The maximum number of records to retrieve, if paging is not used")]
      56:     [DefaultValue(0)]
      57:     public Int32 MaximumRecords
      58:     {
      59:         get;
      60:         set;
      61:     }
      62:  
      63:     [Description("The page size to retrieve")]
      64:     [DefaultValue(0)]
      65:     public Int32 PageSize
      66:     {
      67:         get;
      68:         set;
      69:     }
      70:  
      71:     [Description("The page index to retrieve")]
      72:     [DefaultValue(0)]
      73:     public Int32 PageIndex
      74:     {
      75:         get;
      76:         set;
      77:     }
      78:  
      79:     [Description("Use HQL or EntityName for selecting")]
      80:     [DefaultValue(NHibernateDataSourceMode.Linq)]
      81:     public NHibernateDataSourceMode Mode
      82:     {
      83:         get;
      84:         set;
      85:     }
      86:  
      87:     [Description("Whether to merge the entity before updating or deleting or not")]
      88:     [DefaultValue(false)]
      89:     public Boolean RefreshBeforeUpdate
      90:     {
      91:         get;
      92:         set;
      93:     }
      94:  
      95:     [Description("Parameters that will be used for the HQL query")]
      96:     public ParameterCollection SelectParameters
      97:     {
      98:         get;
      99:         private set;
     100:     }
     101:  
     102:     [Description("Parameters that will be used for inserting a new entity")]
     103:     public ParameterCollection InsertParameters
     104:     {
     105:         get;
     106:         private set;
     107:     }
     108:  
     109:     [Description("Parameters that will be used for updating an existing entity")]
     110:     public ParameterCollection UpdateParameters
     111:     {
     112:         get;
     113:         private set;
     114:     }
     115:  
     116:     [Description("Parameters that will be used for deleting an existing entity")]
     117:     public ParameterCollection DeleteParameters
     118:     {
     119:         get;
     120:         private set;
     121:     }
     122:  
     123:     [Browsable(false)]
     124:     public ISessionFactory SessionFactory
     125:     {
     126:         get;
     127:         set;
     128:     }
     129:  
     130:     internal static ISessionFactory InternalSessionFactory
     131:     {
     132:         get;
     133:         set;
     134:     }
     135:  
     136:     internal ISessionFactory EffectiveSessionFactory
     137:     {
     138:         get
     139:         {
     140:             var sessionFactory = this.SessionFactory ?? InternalSessionFactory;
     141:             var sfArgs = new BuildSessionFactoryEventArgs() { SessionFactory = sessionFactory };
     142:  
     143:             this.OnBuildSessionFactory(sfArgs);
     144:  
     145:             if (sfArgs.SessionFactory == null)
     146:             {
     147:                 var cfg = new Configuration().Configure();
     148:  
     149:                 var cfgArgs = new ConfigureEventArgs() { Configuration =  cfg };
     150:  
     151:                 this.OnConfigure(cfgArgs);
     152:  
     153:                 cfg = cfgArgs.Configuration;
     154:  
     155:                 sessionFactory = cfg.BuildSessionFactory();
     156:  
     157:                 if (InternalSessionFactory == null)
     158:                 {
     159:                     InternalSessionFactory = sessionFactory;
     160:                 }
     161:             }
     162:             else
     163:             {
     164:                 sessionFactory = sfArgs.SessionFactory;
     165:             }
     166:  
     167:             return (sessionFactory);
     168:         }
     169:     }
     170:  
     171:     protected virtual void OnBuildSessionFactory(BuildSessionFactoryEventArgs e)
     172:     {
     173:         var handler = this.BuildSessionFactory;
     174:  
     175:         if (handler != null)
     176:         {
     177:             handler(this, e);
     178:         }
     179:     }
     180:  
     181:     protected virtual void OnConfigure(ConfigureEventArgs e)
     182:     {
     183:         var handler = this.Configure;
     184:  
     185:         if (handler != null)
     186:         {
     187:             handler(this, e);
     188:         }
     189:     }
     190:  
     191:     protected virtual void OnCreateInstance(CreateInstanceEventArgs e)
     192:     {
     193:         var handler = this.CreateInstance;
     194:  
     195:         if (handler != null)
     196:         {
     197:             handler(this, e);
     198:         }
     199:     }
     200:  
     201:     protected virtual void OnEntitiesSelecting(EntitiesSelectingEventArgs e)
     202:     {
     203:         var handler = this.EntitiesSelecting;
     204:  
     205:         if (handler != null)
     206:         {
     207:             handler(this, e);
     208:         }
     209:     }
     210:  
     211:     protected virtual void OnEntityInserted(EntityEventArgs e)
     212:     {
     213:         var handler = this.EntityInserted;
     214:  
     215:         if (handler != null)
     216:         {
     217:             handler(this, e);
     218:         }
     219:     }
     220:  
     221:     protected virtual void OnEntityDeleted(EntityEventArgs e)
     222:     {
     223:         var handler = this.EntityDeleted;
     224:  
     225:         if (handler != null)
     226:         {
     227:             handler(this, e);
     228:         }
     229:     }
     230:  
     231:     protected virtual void OnEntityUpdated(EntityEventArgs e)
     232:     {
     233:         var handler = this.EntityUpdated;
     234:  
     235:         if (handler != null)
     236:         {
     237:             handler(this, e);
     238:         }
     239:     }
     240:  
     241:     protected virtual void OnEntityInserting(EntityEventArgs e)
     242:     {
     243:         var handler = this.EntityInserting;
     244:  
     245:         if (handler != null)
     246:         {
     247:             handler(this, e);
     248:         }
     249:     }
     250:  
     251:     protected virtual void OnEntityDeleting(EntityEventArgs e)
     252:     {
     253:         var handler = this.EntityDeleting;
     254:  
     255:         if (handler != null)
     256:         {
     257:             handler(this, e);
     258:         }
     259:     }
     260:  
     261:     protected virtual void OnEntityUpdating(EntityEventArgs e)
     262:     {
     263:         var handler = this.EntityUpdating;
     264:  
     265:         if (handler != null)
     266:         {
     267:             handler(this, e);
     268:         }
     269:     }
     270:  
     271:     public virtual void OnEntitiesSelected(EntitiesSelectedEventArgs e)
     272:     {
     273:         var handler = this.EntitiesSelected;
     274:  
     275:         if (handler != null)
     276:         {
     277:             handler(this, e);
     278:         }
     279:     }
     280:  
     281:     public virtual void OnOperationCompleted(OperationCompletedEventArgs e)
     282:     {
     283:         var handler = this.OperationCompleted;
     284:  
     285:         if (handler != null)
     286:         {
     287:             handler(this, e);
     288:         }
     289:     }
     290:  
     291:     public Int32 Insert()
     292:     {
     293:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     294:         {
     295:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     296:         }
     297:  
     298:         if (this.InsertParameters.Count == 0)
     299:         {
     300:             throw (new InvalidOperationException("Missing InsertParameters."));
     301:         }
     302:  
     303:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     304:         using (session.BeginTransaction())
     305:         {
     306:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.InsertParameters));
     307:  
     308:             this.OnEntityInserting(args);
     309:  
     310:             if (args.Cancel == true)
     311:             {
     312:                 return (0);
     313:             }
     314:  
     315:             session.Insert(args.Entity);
     316:             session.Transaction.Commit();
     317:  
     318:             this.OnEntityInserted(args);
     319:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Insert, args.Entity));
     320:         }
     321:  
     322:         return (1);
     323:     }
     324:  
     325:     public Int32 Update()
     326:     {
     327:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     328:         {
     329:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     330:         }
     331:  
     332:         if (this.UpdateParameters.Count == 0)
     333:         {
     334:             throw (new InvalidOperationException("Missing UpdateParameters."));
     335:         }
     336:  
     337:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     338:         using (session.BeginTransaction())
     339:         {
     340:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.UpdateParameters));
     341:  
     342:             this.OnEntityUpdating(args);
     343:  
     344:             if (args.Cancel == true)
     345:             {
     346:                 return (0);
     347:             }
     348:  
     349:             if (this.RefreshBeforeUpdate == true)
     350:             {
     351:                 this.Refresh(args.Entity);
     352:             }
     353:  
     354:             session.Update(args.Entity);
     355:             session.Transaction.Commit();
     356:  
     357:             this.OnEntityUpdated(args);
     358:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Update, args.Entity));
     359:  
     360:             return (1);
     361:         }
     362:     }
     363:  
     364:     public Int32 Delete()
     365:     {
     366:         if (String.IsNullOrWhiteSpace(this.EntityName) == true)
     367:         {
     368:             throw (new InvalidOperationException("The EntityName property cannot be empty."));
     369:         }
     370:  
     371:         using (var session = this.EffectiveSessionFactory.OpenStatelessSession())
     372:         using (session.BeginTransaction())
     373:         {
     374:             var args = new EntityEventArgs(this.CreateInstanceAndSetParameters(this.DeleteParameters));
     375:  
     376:             this.OnEntityDeleting(args);
     377:  
     378:             if (args.Cancel == true)
     379:             {
     380:                 return (0);
     381:             }
     382:  
     383:             if (this.RefreshBeforeUpdate == true)
     384:             {
     385:                 this.Refresh(args.Entity);
     386:             }
     387:  
     388:             session.Delete(args.Entity);
     389:             session.Transaction.Commit();
     390:  
     391:             this.OnEntityDeleted(args);
     392:             this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Delete, args.Entity));
     393:  
     394:             return (1);
     395:         }
     396:     }
     397:  
     398:     protected void Refresh(Object entity)
     399:     {
     400:         using (var session = this.EffectiveSessionFactory.OpenSession())
     401:         {
     402:             session.DefaultReadOnly = true;
     403:             session.FlushMode = FlushMode.Never;
     404:  
     405:             var metadata = this.GetMetadata(this.EntityName);
     406:             var propertiesToLoad = new List<String>();
     407:  
     408:             for (var i = 0; i < metadata.PropertyNames.Length; ++i)
     409:             {
     410:                 if (metadata.GetPropertyValue(entity, metadata.PropertyNames[i], EntityMode.Poco) == null)
     411:                 {
     412:                     if (metadata.PropertyTypes[i].IsEntityType == false)
     413:                     {
     414:                         propertiesToLoad.Add(metadata.PropertyNames[i]);
     415:                     }
     416:                     else
     417:                     {
     418:                         propertiesToLoad.Add(String.Concat(metadata.PropertyNames[i], ".id"));
     419:                     }
     420:                 }
     421:             }
     422:  
     423:             var hql = new StringBuilder();
     424:             hql.Append("select ");
     425:             hql.Append(String.Join(", ", propertiesToLoad));
     426:             hql.AppendFormat(" from {0} where id = :id", entity.GetType().FullName);
     427:  
     428:             var query = session.CreateQuery(hql.ToString());
     429:             query.SetParameter("id", metadata.GetIdentifier(entity, EntityMode.Poco));
     430:  
     431:             var result = query.UniqueResult();
     432:             var values = (result as Object[]) ?? new Object[] { result };
     433:  
     434:             for (var i = 0; i < propertiesToLoad.Count; ++i)
     435:             {
     436:                 var parts = propertiesToLoad[i].Split('.');
     437:                 var value = values[i];
     438:                 var propertyName = parts.First();
     439:  
     440:                 if (parts.Length > 1)
     441:                 {
     442:                     var propertyIndex = Array.IndexOf(metadata.PropertyNames, propertyName);
     443:                     var propertyType = metadata.PropertyTypes[propertyIndex].ReturnedClass;
     444:  
     445:                     value = session.Load(propertyType, values[i]);
     446:                 }
     447:  
     448:                 metadata.SetPropertyValue(entity, propertyName, value, EntityMode.Poco);
     449:             }
     450:         }
     451:     }
     452:  
     453:     protected internal IDictionary<String, Object> GetParameters(ParameterCollection parameters)
     454:     {
     455:         return (parameters.GetValues(this.Context, this).OfType<DictionaryEntry>().ToDictionary(x => x.Key.ToString(), x => x.Value));
     456:     }
     457:  
     458:     protected void SetParameterValues(Object instance, IClassMetadata metadata, IDictionary<String, Object> parameters)
     459:     {
     460:         foreach (var parameter in parameters)
     461:         {
     462:             if (metadata.PropertyNames.Contains(parameter.Key) == true)
     463:             {
     464:                 metadata.SetPropertyValue(instance, parameter.Key, parameter.Value, EntityMode.Poco);
     465:             }
     466:             else if (metadata.IdentifierPropertyName == parameter.Key)
     467:             {
     468:                 metadata.SetIdentifier(instance, parameter.Value, EntityMode.Poco);
     469:             }
     470:         }
     471:     }
     472:  
     473:     protected Object CreateInstanceAndSetParameters(ParameterCollection parameters)
     474:     {
     475:         var metadata = this.GetMetadata(this.EntityName);
     476:  
     477:         if (metadata == null)
     478:         {
     479:             throw (new InvalidOperationException("Entity could not be found."));
     480:         }
     481:  
     482:         var entityType = metadata.GetMappedClass(EntityMode.Poco);
     483:  
     484:         var ciArgs = new CreateInstanceEventArgs(entityType, null);
     485:  
     486:         this.OnCreateInstance(ciArgs);
     487:  
     488:         if (ciArgs.Instance == null)
     489:         {
     490:             ciArgs.Instance = Activator.CreateInstance(entityType);
     491:         }
     492:  
     493:         this.SetParameterValues(ciArgs.Instance, metadata, this.GetParameters(parameters));
     494:  
     495:         return (ciArgs.Instance);
     496:     }
     497:  
     498:     protected internal IClassMetadata GetMetadata(String entityName)
     499:     {
     500:         var metadata = this.EffectiveSessionFactory.GetAllClassMetadata().Where(x => x.Key.EndsWith(entityName)).Select(x => x.Value).SingleOrDefault();
     501:  
     502:         return (metadata);
     503:     }
     504:  
     505:     protected internal void ProcessEntitiesSelecting(EntitiesSelectingEventArgs e)
     506:     {
     507:         this.OnEntitiesSelecting(e);
     508:     }
     509:  
     510:     protected internal void ProcessEntitiesSelected(EntitiesSelectedEventArgs e)
     511:     {
     512:         this.OnEntitiesSelected(e);
     513:         this.OnOperationCompleted(new OperationCompletedEventArgs(DataSourceOperation.Select, e.Results));
     514:     }
     515:  
     516:     protected override QueryableDataSourceView CreateQueryableView()
     517:     {
     518:         return (new NHibernateDataSourceView(this, "DefaultView", this.Context) as QueryableDataSourceView);
     519:     }
     520: }

    You can see that it exposes some events:

    • Configure: gives developers a chance to build (or return an existing) Configuration instance, that will be used for building the session factory;
    • BuildSessionFactory: allows setting parameters on the default session factory or returning an existing one;
    • CreateInstance: raised before NHibernate creates a default instance, to allow developers to return one;
    • EntityInserting: raised before an entity is inserted, allowing developers to cancel the operations or to set entity parameter;
    • EntityUpdating: raised before an entity is updated, allowing developers to cancel the operations or to set entity parameter;
    • EntityDeleting: raised before an entity is deleting, allowing its cancellation;
    • EntitiesSelecting: raised before a select operation is performed;
    • EntityInserted: raised after an entity was inserted;
    • EntityUpdated: raised after an entity was updated;
    • EntityDeleted: raised after an entity was deleted;
    • EntitiesSelected: raised after a select operation was performed;
    • OperationCompleted: raised after an operation completes (select, insert, update or delete).


    If no handler for CreateInstance is supplied, NHibernateDataSource will try to create an entity using Activator.CreateInstance.

    EntitySelecting is raised regardless of the Mode (Hql or Linq), but it will have different values in its argument: a query string plus parameters in the case of Hql and an IQueryable instance for Linq.

    EntityInserting, EntityUpdating and EntityDeleting allow the modification of properties of the entity in the parameter, but not the replacing of the entity itself.

    OperationCompleted is always called, except in the event of an exception.

    It also exposes a couple of properties:

    • Mode: one of the two operation modes, Hql or Linq. If Hql is used, then the Hql property must be set; otherwise, it’s EntityName that is required;
    • Hql: an NHibernate HQL query string;
    • EntityName: the name of an entity that the control will work with; only required for Mode Linq or for inserts, updates or deletes;
    • RefreshBeforeUpdate: whether NHibernate should refresh the properties of an entity before updating or deleting it;
    • MaximumRecords: the optional maximum number of records to retrieve, if paging is not used (PageSize and PageIndex);
    • PageIndex: the page index to retrieve;
    • PageSize: the page size to retrieve;
    • SessionFactory: a session factory that will be used instead of a default created one;
    • SelectParameters: a collection of parameters to be applied to the Hql string;
    • InsertParameters: a collection of parameters for the insert operation;
    • UpdateParameters: a collection of parameters for the update operation;
    • DeleteParameters: a collection of parameters for the delete operation.


    And, of course, exposes the basic operations: select is the default, but Insert, Update and Delete methods are available.

    NHibernateDataSource will check if the SessionFactory property is set, otherwise, it will build its own Configuration instance and raise the Configure and BuildSessionFactory events. The generated session factory is then stored in the InternalSessionFactory static property for caching.

    Then, the NHibernateDataSourceView, which is the responsible for the actual querying, inheriting from QueryableDataSourceView:

       1: public class NHibernateDataSourceView : QueryableDataSourceView
       2: {
       3:     private static readonly MethodInfo queryMethod = typeof (LinqExtensionMethods).GetMethod("Query", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(IStatelessSession) }, null );
       4:     private static readonly MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList", BindingFlags.Public | BindingFlags.Static);
       5:  
       6:     public NHibernateDataSourceView(NHibernateDataSource dataSource, String viewName, HttpContext context) : base(dataSource, viewName, context)
       7:     {
       8:         this.DataSource = dataSource;
       9:     }
      10:  
      11:     protected NHibernateDataSource DataSource
      12:     {
      13:         get;
      14:         private set;
      15:     }
      16:  
      17:     protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
      18:     {
      19:         using (var session = this.DataSource.EffectiveSessionFactory.OpenStatelessSession())
      20:         {
      21:             var results = null as IList;
      22:  
      23:             switch (this.DataSource.Mode)
      24:             {
      25:                 case NHibernateDataSourceMode.Hql:
      26:                 {
      27:                     if (String.IsNullOrWhiteSpace(this.DataSource.Hql) == true)
      28:                     {
      29:                         throw (new InvalidOperationException("The Hql property cannot be empty."));
      30:                     }
      31:  
      32:                     var hql = this.DataSource.Hql;
      33:                     var parameters = this.DataSource.GetParameters(this.DataSource.SelectParameters);
      34:                     var args = new EntitiesSelectingEventArgs(hql, parameters, this.DataSource.PageSize, this.DataSource.PageIndex, this.DataSource.MaximumRecords);
      35:  
      36:                     this.DataSource.ProcessEntitiesSelecting(args);
      37:  
      38:                     var query = session.CreateQuery(args.Hql);
      39:  
      40:                     foreach (var param in args.SelectParameters)
      41:                     {
      42:                         if (!(param.Value is IEnumerable) || (param.Value is String) || (param.Value is Byte[]))
      43:                         {
      44:                             query.SetParameter(param.Key, param.Value);
      45:                         }
      46:                         else
      47:                         {
      48:                             query.SetParameterList(param.Key, param.Value as IEnumerable);
      49:                         }
      50:                     }
      51:  
      52:                     if (args.PageSize != 0)
      53:                     {
      54:                         query.SetMaxResults(args.PageSize);
      55:                         query.SetFirstResult(Math.Max((args.PageIndex * args.PageSize) - 1, 0));
      56:                         arguments.MaximumRows = args.PageSize;
      57:                     }
      58:  
      59:                     if (args.MaximumRecords != 0)
      60:                     {
      61:                         query.SetMaxResults(args.MaximumRecords);
      62:                         arguments.MaximumRows = args.MaximumRecords;
      63:                     }
      64:  
      65:                     results = query.List();
      66:  
      67:                     arguments.AddSupportedCapabilities(DataSourceCapabilities.Page);
      68:  
      69:                     if (args.PageSize != 0)
      70:                     {
      71:                         arguments.StartRowIndex = Math.Max((args.PageIndex * args.PageSize) - 1, 0);
      72:                     }
      73:  
      74:                     break;
      75:                 }
      76:  
      77:                 case NHibernateDataSourceMode.Linq:
      78:                 {
      79:                     if (String.IsNullOrWhiteSpace(this.DataSource.EntityName) == true)
      80:                     {
      81:                         throw (new InvalidOperationException("The EntityName property cannot be empty."));
      82:                     }
      83:  
      84:                     var query = queryMethod.MakeGenericMethod(this.EntityType).Invoke(null, new Object[] { session }) as IQueryable;
      85:  
      86:                     var qcea = new QueryCreatedEventArgs(query);
      87:  
      88:                     this.OnQueryCreated(qcea);
      89:  
      90:                     var esaea = new EntitiesSelectingEventArgs(qcea.Query);
      91:  
      92:                     this.DataSource.ProcessEntitiesSelecting(esaea);
      93:  
      94:                     results = toListMethod.MakeGenericMethod(this.EntityType).Invoke(null, new Object[] { esaea.Query }) as IList;
      95:  
      96:                     arguments.AddSupportedCapabilities(DataSourceCapabilities.Page | DataSourceCapabilities.Sort);
      97:  
      98:                     break;
      99:                 }
     100:             }
     101:  
     102:             var entitiesSelectedArgs = new EntitiesSelectedEventArgs(results);
     103:  
     104:             this.DataSource.ProcessEntitiesSelected(entitiesSelectedArgs);
     105:  
     106:             return (entitiesSelectedArgs.Results);
     107:         }
     108:     }
     109:  
     110:     protected override Type EntityType
     111:     {
     112:         get
     113:         {
     114:             return (this.DataSource.GetMetadata(this.DataSource.EntityName).GetMappedClass(EntityMode.Poco));
     115:         }
     116:     }
     117:  
     118:     protected override Object GetSource(QueryContext context)
     119:     {
     120:         throw new NotImplementedException();
     121:     }
     122:  
     123:     protected override void HandleValidationErrors(IDictionary<String, Exception> errors, DataSourceOperation operation)
     124:     {
     125:     }
     126: }

    And the NHibernateDataSourceMode:

       1: public enum NHibernateDataSourceMode
       2: {
       3:     Linq,
       4:     Hql
       5: }

    Finally, all of the event arguments:

       1: [Serializable]
       2: public sealed class BuildSessionFactoryEventArgs : EventArgs
       3: {
       4:     public ISessionFactory SessionFactory
       5:     {
       6:         get;
       7:         set;
       8:     }
       9: }
      10:  
      11: [Serializable]
      12: public sealed class ConfigureEventArgs : EventArgs
      13: {
      14:     public Configuration Configuration
      15:     {
      16:         get;
      17:         set;
      18:     }
      19: }
      20:  
      21: [Serializable]
      22: public sealed class CreateInstanceEventArgs : EventArgs
      23: {
      24:     public CreateInstanceEventArgs(Type type, Object instance)
      25:     {
      26:         this.Type = type;
      27:         this.Instance = instance;
      28:     }
      29:  
      30:     public Type Type
      31:     {
      32:         get;
      33:         private set;
      34:     }
      35:  
      36:     public Object Instance
      37:     {
      38:         get;
      39:         set;
      40:     }
      41: }
      42:  
      43: [Serializable]
      44: public sealed class EntitiesSelectedEventArgs : EventArgs
      45: {
      46:     public EntitiesSelectedEventArgs(IList results)
      47:     {
      48:         this.Results = results;
      49:     }
      50:  
      51:     public IList Results
      52:     {
      53:         get;
      54:         set;
      55:     }
      56: }
      57:  
      58: [Serializable]
      59: public sealed class EntitiesSelectingEventArgs : EventArgs
      60: {
      61:     public EntitiesSelectingEventArgs(IQueryable query)
      62:     {
      63:         this.Query = query;
      64:     }
      65:  
      66:     public EntitiesSelectingEventArgs(String hql, IDictionary<String, Object> selectParameters, Int32 pageSize, Int32 pageIndex, Int32 maximumRecords)
      67:     {
      68:         this.Hql = hql;
      69:         this.SelectParameters = selectParameters;
      70:         this.PageSize = pageSize;
      71:         this.PageIndex = pageIndex;
      72:         this.MaximumRecords = maximumRecords;
      73:     }
      74:  
      75:     public IQueryable Query
      76:     {
      77:         get;
      78:         set;
      79:     }
      80:  
      81:     public String Hql
      82:     {
      83:         get;
      84:         set;
      85:     }
      86:  
      87:     public IDictionary<String, Object> SelectParameters
      88:     {
      89:         get;
      90:         private set;
      91:     }
      92:  
      93:     public Int32 PageSize
      94:     {
      95:         get;
      96:         set;
      97:     }
      98:  
      99:     public Int32 PageIndex
     100:     {
     101:         get;
     102:         set;
     103:     }
     104:  
     105:     public Int32 MaximumRecords
     106:     {
     107:         get;
     108:         set;
     109:     }
     110: }
     111:  
     112: [Serializable]
     113: public sealed class EntityEventArgs : CancelEventArgs
     114: {
     115:     public EntityEventArgs(Object entity)
     116:     {
     117:         this.Entity = entity;
     118:     }
     119:  
     120:     public Object Entity
     121:     {
     122:         get; 
     123:         set;
     124:     }
     125: }
     126:  
     127: [Serializable]
     128: public sealed class OperationCompletedEventArgs : EventArgs
     129: {
     130:     public OperationCompletedEventArgs(DataSourceOperation operation, Object entity)
     131:     {
     132:         this.Entity = entity;
     133:         this.Operation = operation;
     134:     }
     135:  
     136:     public OperationCompletedEventArgs(DataSourceOperation operation, IList results)
     137:     {
     138:         this.Results = results;
     139:         this.Operation = operation;
     140:     }
     141:  
     142:     public DataSourceOperation Operation
     143:     {
     144:         get;
     145:         private set;
     146:     }
     147:  
     148:     public Object Entity
     149:     {
     150:         get;
     151:         protected set;
     152:     }
     153:  
     154:     public IList Results
     155:     {
     156:         get;
     157:         private set;
     158:     }
     159: }

    Now, let’s see concrete examples of its usage. First, using Mode Hql:

       1: <nh:NHibernateDataSource runat="server" ID="nhds" RefreshBeforeUpdate="true" EntityName="Product" Mode="Hql" Hql="from Product p where size(p.OrderDetails) > :size">
       2:     <SelectParameters>
       3:         <asp:Parameter Name="size" DefaultValue="1" Type="Int32" />
       4:     </SelectParameters>
       5:     <InsertParameters>
       6:         <asp:Parameter Name="Name" DefaultValue="Some Name" Type="String" />
       7:         <asp:Parameter Name="Price" DefaultValue="100" Type="Decimal" />
       8:     </InsertParameters>
       9:     <UpdateParameters>
      10:         <asp:QueryStringParameter Name="ProductId" QueryStringField="ProductId" Type="Int32" />
      11:         <asp:QueryStringParameter Name="Price" DefaultValue="50" Type="Decimal" />
      12:     </UpdateParameters>
      13:     <DeleteParameters>
      14:         <asp:QueryStringParameter Name="ProductId" QueryStringField="ProductId" Type="Int32" />
      15:     </DeleteParameters>
      16: </nh:NHibernateDataSource>

    You can see that the Hql property has a parameter, price, which is bound to a parameter in SelectParameters with the same name. Each parameter is an instance of the Parameter class, here I am using a parameter with a static value (Parameter) and another that takes a value from the query string (QueryStringParameter), but others exist. To help with NHibernate insert and update operations, I created a new Parameter class, EntityParameter, that knows how to retrieve a en entity or a proxy to an entity:

       1: public sealed class EntityParameter : Parameter
       2: {
       3:     public EntityParameter()
       4:     {
       5:         this.Lazy = true;
       6:     }
       7:  
       8:     public String EntityName
       9:     {
      10:         get;
      11:         set;
      12:     }
      13:  
      14:     public Object Id
      15:     {
      16:         get;
      17:         set;
      18:     }
      19:  
      20:     public Boolean Lazy
      21:     {
      22:         get;
      23:         set;
      24:     }
      25:  
      26:     protected override Parameter Clone()
      27:     {
      28:         return (new EntityParameter(){ EntityName = this.EntityName, Id = this.Id });
      29:     }
      30:  
      31:     protected override Object Evaluate(HttpContext context, Control control)
      32:     {
      33:         var dataSource = control as NHibernateDataSource;
      34:  
      35:         if (dataSource == null)
      36:         {
      37:             throw (new InvalidOperationException("EntityParameter can only be used with NHibernateDataSource."));
      38:         }
      39:  
      40:         using (var session = dataSource.EffectiveSessionFactory.OpenStatelessSession())
      41:         {
      42:             var metadata = dataSource.GetMetadata(this.EntityName);
      43:  
      44:             if (metadata == null)
      45:             {
      46:                 throw (new InvalidOperationException("Entity could not be found."));
      47:             }
      48:  
      49:             var entityType = metadata.GetMappedClass(EntityMode.Poco);
      50:             var idType = metadata.IdentifierType.ReturnedClass;
      51:             var id = Convert.ChangeType(this.Id, idType);
      52:             var entity = (this.Lazy == true) ? (metadata as IEntityPersister).CreateProxy(id, session.GetSessionImplementation()) : session.Get(entityType.FullName, id);
      53:  
      54:             return (entity);
      55:         }
      56:     }
      57: }

    As for Mode Linq, an example using a QueryExtender is in order:

       1: <asp:TextBox runat="server" ID="name"/>
       2:  
       3: <asp:QueryExtender runat="server" TargetControlID="nhds">
       4:     <asp:SearchExpression DataFields="Name" SearchType="StartsWith">
       5:         <asp:ControlParameter ControlID="name" />
       6:     </asp:SearchExpression>
       7:     <asp:OrderByExpression DataField="Price" Direction="Descending" />
       8: </asp:QueryExtender>
       9:  
      10: <nh:NHibernateDataSource runat="server" ID="nhds" RefreshBeforeUpdate="true" EntityName="Product" Mode="Linq">
      11:     <InsertParameters>
      12:         <asp:Parameter Name="Name" DefaultValue="Some Name" Type="String" />
      13:         <asp:Parameter Name="Price" DefaultValue="100" Type="Decimal" />
      14:     </InsertParameters>
      15:     <UpdateParameters>
      16:         <asp:QueryStringParameter QueryStringField="ProductId" Name="ProductId" Type="Int32" />
      17:         <asp:Parameter Name="Price" DefaultValue="50" Type="Decimal" />
      18:     </UpdateParameters>
      19:     <DeleteParameters>
      20:         <asp:QueryStringParameter QueryStringField="ProductId" Name="ProductId" Type="Int32" />
      21:     </DeleteParameters>
      22: </nh:NHibernateDataSource>

    The LINQ query produced by the NHibernateDataSource is intercepted by the QueryExtender and a where (SearchExpression) and a order by (OrderByExpression) clauses are added to it. Other expressions can be used, inheriting from DataSourceExpression, and some take parameters of type Parameter. Do notice that filtering and sorting is performed server-side, not client-side.

    Of course, this can certainly be improved, let me hear your thoughts and questions.

    And that’s it. Enjoy!

    Read more...

  • Book Reviews

    This page will list all of my book reviews, in no particular order.

    Right now, I am reviewing Microsoft Azure Development Cookbook Second Edition, just give me some more days, because it’s a long one.

    Microsoft Azure Development Cookbook Second Edition

    (Review not yet available)

    https://www.packtpub.com/application-development/microsoft-azure-development-cookbook-second-edition

    Windows Presentation Foundation 4.5 Cookbook

    https://weblogs.asp.net/ricardoperes/windows-presentation-foundation-4-5-cookbook-review

    https://www.packtpub.com/application-development/windows-presentation-foundation-45-cookbook

    NHibernate 2 Beginner's Guide

    https://weblogs.asp.net/ricardoperes/nhibernate-2-beginner-s-guide-review

    https://www.packtpub.com/application-development/nhibernate-2-beginners-guide

    NHibernate 3.0 Cookbook

    https://weblogs.asp.net/ricardoperes/nhibernate-3-0-cookbook-review

    https://www.packtpub.com/application-development/nhibernate-30-cookbook

    Visual Studio 2012 and .NET 4.5 Expert Development Cookbook

    https://weblogs.asp.net/ricardoperes/visual-studio-2012-and-net-4-5-expert-development-cookbook-review

    https://www.packtpub.com/application-development/visual-studio-2012-and-net-45-expert-development-cookbook

    NHibernate in Action Book

    https://weblogs.asp.net/ricardoperes/nhibernate-in-action-book

    http://www.manning.com/kuate/

    Instant StyleCop Code Analysis How-to Review

    https://weblogs.asp.net/ricardoperes/instant-stylecop-code-analysis-how-to-review

    https://www.packtpub.com/hardware-and-creative/instant-stylecop-code-analysis-how-instant

    Developing Windows Store Apps with HTML5 and JavaScript

    https://weblogs.asp.net/ricardoperes/developing-windows-store-apps-with-html5-and-javascript-review

    https://www.packtpub.com/application-development/developing-windows-store-apps-html5-and-javascript

    Programming Entity Framework

    https://weblogs.asp.net/ricardoperes/programming-entity-framework-book

    http://learnentityframework.com/

    Pro WF: Windows Workflow in .NET 3.5

    https://weblogs.asp.net/ricardoperes/pro-wf-windows-workflow-in-net-3-5

    http://www.apress.com/9781430209751

    Read more...

  • Getting the SQL from a LINQ Query in NHibernate

    In case you ever want to have a look at the generated SQL before it is actually executed, you can use this extension method:

       1: public static String ToSql(this IQueryable queryable)
       2: {
       3:     var sessionProperty = typeof(DefaultQueryProvider).GetProperty("Session", BindingFlags.NonPublic | BindingFlags.Instance);
       4:     var session = sessionProperty.GetValue(queryable.Provider, null) as ISession;
       5:     var sessionImpl = session.GetSessionImplementation();
       6:     var factory = sessionImpl.Factory;
       7:     var nhLinqExpression = new NhLinqExpression(queryable.Expression, factory);
       8:     var translatorFactory = new ASTQueryTranslatorFactory();
       9:     var translator = translatorFactory.CreateQueryTranslators(nhLinqExpression, null, false, sessionImpl.EnabledFilters, factory).First();
      10:     //in case you want the parameters as well
      11:     //var parameters = nhLinqExpression.ParameterValuesByName.ToDictionary(x => x.Key, x => x.Value.Item1);
      12:  
      13:     return translator.SQLString;
      14: }

    Just call it on an IQueryable or IQueryable<T> instance, such as the one you got from ISession.Query<T>:

       1: var query = session.Query<Product>().Where(p => p.Price > 100);
       2: var sql = query.ToSql();

    Parameters such as 100 will be located in the nhLinqExpression.ParameterValuesByName collection.

    Read more...

  • NHibernate Pitfalls: Refreshing Manually Created Entities Does Not Bring Lazy Properties of Base Types

    This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

    Suppose you have some properties of an entity, including its id, and you want to refresh its state from the database:

       1: var product = new Product { ProductId = 1 };
       2:  
       3: //product.Image is null
       4:  
       5: session.Refresh(product);
       6:  
       7: //product.Image is null

    Image is a Byte[] and is configured as lazy. The problem is that NHibernate is not capable of returning a proxy for it, because the entity itself is not a proxy.

    This does not happen for associated entities (one-to-one, many-to-one, one-to-many and many-to-many):

       1: var order = new Order { OrderId = 1 };
       2:  
       3: //order.Customer is null
       4:  
       5: session.Refresh(order);
       6:  
       7: //order.Customer is a proxy and accessing the property will load it

    In this case, Customer is another entity, and NHibernate can assign the Order.Customer property a proxy to it.

    Because of this problem, I created a simple extension method that loads all properties. It is even smart enough to use proxies, if we so require it:

       1: public static void InitializeLazyProperties(this ISession session, Object entity, Boolean useProxyWhenPossible = true)
       2: {
       3:     if (entity.IsProxy() == true)
       4:     {
       5:         //if entity is a proxy, use the default mechanism
       6:         NHibernateUtil.Initialize(entity);
       7:     }
       8:     else
       9:     {
      10:         var metadata = session.SessionFactory.GetClassMetadata(entity.GetType());
      11:         var propertiesToLoad = new List<String>();
      12:  
      13:         for (var i = 0; i < metadata.PropertyNames.Length; ++i)
      14:         {
      15:             if (metadata.GetPropertyValue(entity, metadata.PropertyNames[i], session.ActiveEntityMode) == null)
      16:             {
      17:                 if ((metadata.PropertyTypes[i].IsEntityType == false) || (useProxyWhenPossible == false))
      18:                 {
      19:                     //either load the value
      20:                     propertiesToLoad.Add(metadata.PropertyNames[i]);
      21:                 }
      22:                 else
      23:                 {
      24:                     //or the id of the associated entity
      25:                     propertiesToLoad.Add(String.Concat(metadata.PropertyNames[i], ".id"));
      26:                 }
      27:             }
      28:         }
      29:  
      30:         var hql = new StringBuilder();
      31:         hql.Append("select ");
      32:         hql.Append(String.Join(", ", propertiesToLoad));
      33:         hql.AppendFormat(" from {0} where id = :id", entity.GetType());
      34:  
      35:         var query = session.CreateQuery(hql.ToString());
      36:         query.SetParameter("id", metadata.GetIdentifier(entity, session.ActiveEntityMode));
      37:  
      38:         var result = query.UniqueResult();
      39:         var values = result as Object[] ?? new Object[] { result };
      40:  
      41:         for (var i = 0; i < propertiesToLoad.Count; ++i)
      42:         {
      43:             var parts = propertiesToLoad[i].Split('.');
      44:             var value = values[i];
      45:             var propertyName = parts.First();
      46:  
      47:             if (parts.Length > 0)
      48:             {
      49:                 var propertyIndex = Array.IndexOf(metadata.PropertyNames, propertyName);
      50:                 var propertyType = metadata.PropertyTypes[propertyIndex].ReturnedClass;
      51:  
      52:                 //create a proxy
      53:                 value = session.Load(propertyType, values[i]);
      54:             }
      55:  
      56:             metadata.SetPropertyValue(entity, propertyName, value, session.ActiveEntityMode);
      57:         }
      58:     }
      59: }

    Of course, it requires that at leat the id property is set. It can be used as:

       1: var order = new Order { OrderId = 360448 };
       2:  
       3: session.InitializeLazyProperties(order);

    Have fun! Winking smile

    Read more...

  • NHibernate Goodies Coming Soon

    Just for you to know that the NHibernate community hasn’t been sleeping, here’s a list of some of the cool new features that are on the queue for the next NHibernate versions.

    Some are still waiting for review from the NHibernate team and because of that, don’t yet have a target version.

    Common base type for ISession and IStatelessSession (NH-1440)

    A long-sought request, this will allow abstracting the two kinds of sessions. It is scheduled for NH 5.

    IQueryable support for persistent collections (NH-2319)

    Allows querying collections without loading them. For NH 5.

    Method for specifying IType of LINQ parameter (NH-2401)

    Allows one to override the type of a parameter. For NH 4.1.

    Allow injectable/inheritance of Linq query provider (NH-2611)

    Allows replacing the default LINQ provider for a custom one, which may support additional features. It is scheduled for NH 4.1.

    NH cannot load mapping assembly from GAC (NH-2831)

    Now we will be able to load assemblies containing mappings from the GAC. For NH 4.0.2.

    Optimistic Locking in mapping by code (NH-2823)

    Setting the lock mode in a LINQ query, bringing it to pair with the other query APIs. For NH 4.1.

    Linq: NHibernateContext for WCF Data Service (NH-2920)

    For exposing NHibernate in a WCF Data Service. For NH 5.

    Add missing standard Id Generators in Mapping By Code (NH-3404)

    All the id generators will now be available in mapping by code. In NH 4.1.

    Ability to select the root entity in a criteria projection (NH-3435)

    In a Criteria query, it will be possible to select the root entity, from which the query originated. For NH 4.1.

    Allow Session.Query to load entities as read-only (NH-3470)

    Another functionality that was missing in LINQ API. Will be available in NH 4.1.

    Allow query model visitor to be provided through the session factory (NH-3499)

    Another extensibility hook. For NH 4.1.

    Allow Creating of Child Stateless Session (NH-3606)

    Right now, it is only possible to create full stateless sessions from existing ones, this will fix that. For NH 4.1.

    Make default value of FlushMode configurable (NH-3619)

    Sometimes it is useful to have a specific flush mode specified other than the default for all new sessions created. For NH 4.1.

    Strongly Typed Delete (NH-3659)

    Delete from a LINQ expression. For NH 5 only.

    Support Bulk Inserts (NH-3675)

    Allow bulk insertions using each provider’s specific mechanism. For NH 5.

    Query by Example in Linq (NH-3714)

    Using entities as examples for LINQ queries. Will be available in NH 5.

    Handle Information Messages from Server (NH-3724)

    Add the ability to handle information messages from the database server (the InfoMessage event). Will be ready in NH 5.

    Delete in Criteria and QueryOver (NH-3735)

    Delete from an existing Criteria or QueryOver query. Only in NH 5.

    Pass Table Variable as a Input parameter to Stored Procedure (NH-3736)

    Allows passing table-valued parameters to NHibernate SQL queries in SQL Server. Not scheduled yet.

    Read more...

  • Lesser-Known NHibernate Features: Future Queries

    Sometimes it makes sense to send several queries at the same time to the database server. This prevents multiple roundtrips and normally can provide results faster. Knowing this, NHibernate offers two features: multiqueries and future queries. Future queries build upon multiqueries and because they use LINQ, which is probably the most common querying API in NHibernate, I will focus in this.

    In order to use future queries, you queue them, by calling the ToFuture() or ToFutureValue() extension methods on any number of queries that you wish to retrieve together:

    1: var futureProducts = session.Query<Product>().Where(p => p.Price > 1000)

    .ToFuture();

       2:  
       3: var futureCustomers = session.Query<Customer>().Where(c => c.Orders.Any()).ToFuture();
       4:  
       5: //no query is sent to the database

    Only after you actually access its results are the queries sent:

       1: var productsCount = futureProducts.Count();

    And the generated SQL is:

       1: select
       2:     product0_.product_id as product1_6_,
       3:     product0_.name as name6_,
       4:     product0_.price as price6_
       5: from
       6:     product product0_
       7: where
       8:     product0_.price>@p0;
       9: select
      10:     customer0_.customer_id as customer1_3_,
      11:     customer0_.name as name3_,
      12:     customer0_.email as email3_
      13: from
      14:     customer customer0_
      15: where
      16:     exists (
      17:         select
      18:             orders1_.order_id
      19:         from
      20:             [
      21:         order] orders1_ where
      22:             customer0_.customer_id=orders1_.customer_id
      23:     );
      24: ;
      25: @p0 = 1000 [Type: Decimal (0)]

    Notice how the two queries are sent at the same time, separated by a semicolon.

    It also works for single values, they can be combined with multi value queries:

    1: var mostExpensiveFutureProduct = session.Query<Product>()

    .OrderByDescending(x => x.Price).Take(1).ToFutureValue();

    The caveat is that this, like multiqueries, is right now only supported in SQL Server, Oracle and MySQL. In all the other cases the results will be retrieved immediately.





    Read more...

  • Lesser-Known NHibernate Features: Entity Mode Map

    This one is a real treat!

    When we use NHibernate or any other O/RM, we normally use classes to represent the database objects (tables or views). In NHibernate, this is called Entity Mode POCOPlain Old CLR Object. NHibernate, however, supports other Entity Mode: Map. This Entity Mode does not use “physical” classes, but instead, dictionaries (implementations of IDictionary)! This makes it great for mapping dynamic database objects without the need for real classes.

    For creating a Map entity, we need to do a slight change in our usual mappings, here is a simple Parent-Child example:

       1: <?xml version="1.0" encoding="utf-8" ?>
       2: <hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">
       3:     <class entity-name="Parent" table="`PARENT`">
       4:         <id name="Name" column="`NAME`" type="String" length="50" generator="assigned"/>
       5:         <set name="Children" lazy="true" inverse="true" cascade="all-delete-orphan">
       6:             <key column="`PARENT_NAME`" not-null="true"/>
       7:             <one-to-many entity-name="Child"/>
       8:         </set>
       9:     </class>
      10:     <class entity-name="Child" table="`CHILD`">
      11:         <id name="Name" column="`NAME`" type="String" length="50" generator="assigned"/>
      12:         <many-to-one name="Parent" entity-name="Parent" column="`PARENT_NAME`" not-null="true"/>
      13:     </class>
      14: </hibernate-mapping>

    You might not have noticed that I used entity-name instead of name in the <class> declaration, in <one-to-many> and <many-to-one>; this tells NHibernate that these classes will be mapped as Map, not POCO (the default). Alas, you cannot use mapping by code, because there is no physical class to map.

    OK, so now we have a mapped Parent and Child. We insert them as this:

       1: using (var session = sessionFactory.OpenSession())
       2: {
       3:     var parent = new Hashtable { { "Name", "Parent Name" } };
       4:     parent["Children"] = new HashSet<Object> { new Hashtable { { "Name", "Child Name" }, { "Parent", parent } } };
       5:  
       6:     session.Save("Parent", parent);
       7:     session.Flush();
       8: }

    I am using Hashtable as an IDictionary implementation, but you might as well use Dictionary<TKey, TValue>. It doesn’t really matter.

    As for retrieving, also simple, just use the ISession methods that take an entityName parameter:

       1: var parentProxy = session.Load("Parent", "Parent Name") as IDictionary;
       2:  
       3: var parent = session.Get("Parent", "Parent Name") as IDictionary;

    Each of these IDictionary instances will have an entry that has key “$type$” and contains the entity type name – in our case, either “Parent” or “Child” – as well as entries for each of the other mapped properties, associations and collections:

       1: var name = parent["Name"] as String;
       2: var children = parent["Children"] as ISet<Object>;

    Entity Mode Map supports both HQL and Criteria querying APIs:

       1: var parentsByCriteria = session.CreateCriteria("Parent").Add(Restrictions.Eq("Name", "Parent Name")).List();
       2:  
       3: var parentsByHQL = session.CreateQuery("from Parent where Name = :name").SetParameter("name", "Parent Name").List();

    And it also supports executable HQL:

       1: session.CreateQuery("delete from Child").ExecuteUpdate();

    Cool, don’t you thing? Eat your heart out, Unnamed Framework! Winking smile

    Read more...

  • Lesser-Known NHibernate Features: Mixing Client and Server-Side Calls in Projections

    Another not widely known feature of NHibernate: mixing client and server-side calls.

    To illustrate this, imagine that you want to calculate the difference between a mapped DateTime property and the current time, DateTime.Now. The following query works in NHibernate, but not in other O/RM frameworks:

       1: var timespans = session.Query<Order>().Select(x => DateTime.Now - x.Date).ToList();

    The generated SQL will only be:

       1: select
       2:     order0_.[date] as col_0_0_
       3: from
       4:     [
       5: order] order0_

    But NHibernate will detect that the result needs to be combined with something from the client side and will return a TimeSpan. This mechanism is extensible – more on this in a future post.

    In other O/RMs, which shall remain unnamed, you have to use LINQ to Objects after LINQ to Unnamed Framework:

       1: ctx.Orders.Select(x => new { ElapsedTime = SqlFunctions.DateDiff("HOUR", x.Date, DateTime.Now) }).ToList().Select(x => TimeSpan.FromHours(x.EllapsedTime));

    Pretty cool, don’t you think? Winking smile

    Read more...

  • Lesser-Known NHibernate Features: Mapping a Class to a Query

    Today I start a new series on lesser-known NHibernate features.

    Besides the normal mapping of a class to a table (or view, for that matter), it is also possible to map a class to a query. Do note that this is not the same as having a class mapped to a table and using custom SQL for the INSERTs, UPDATEs, SELECTs or DELETEs, this is something different. This is called Subselect mapping - not to be confused with subselect fetching.

    To demonstrate this, imagine we have a blogging model where a Blog has several Posts. We can map a readonly query between the two as (in HBM.XML):

       1: <?xml version="1.0" encoding="utf-8"?>
       2: <hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">
       3:     <class name="BlogPost" mutable="false">
       4:         <subselect>
       5:             <![CDATA[
       6:             SELECT blog_id, (SELECT COUNT(1) FROM post WHERE post.blog_id = blog.blog_id) post_count FROM blog
       7:             ]]>
       8:         </subselect>
       9:         <synchronize table="blog"/>
      10:         <synchronize table="post"/>
      11:         <id column="blog_id" name="BlogId"/>
      12:         <property name="PostCount" column="post_count"/>
      13:     </class>
      14: </hibernate-mapping>

    In mapping by code, it would be:

       1: public class BlogPostMapping : ClassMapping<BlogPost>
       2: {
       3:     public BlogPostMapping()
       4:     {
       5:         this.Subselect("SELECT blog_id, (SELECT COUNT(1) FROM post WHERE post.blog_id = blog.blog_id) post_count FROM blog");
       6:         this.Mutable(false);
       7:         this.Synchronize("blog", "post");
       8:         this.Id(x => x.BlogId, x => x.Column("blog_id"));
       9:         this.Property(x => x.PostCount, x => x.Column("post_count"));
      10:     }
      11: }

    It easy to understand that the class cannot be mutable: it makes no sense to change any of the properties, because they may not map directly to a table.

    Querying is done in exactly the same way:

       1: session.Query<BlogPost>().ToList();

    And the resulting SQL is:

       1: select
       2:     blogpost0_.blog_id as blog1_19_,
       3:     blogpost0_.post_count as post2_19_
       4: from
       5:     ( SELECT
       6:         blog_id,
       7:         (SELECT
       8:             COUNT(1)
       9:         FROM
      10:             post
      11:         WHERE
      12:             post.blog_id = blog.blog_id) post_count
      13:     FROM
      14:         blog ) blogpost0_

    Read more...

  • NHibernate Pitfalls: Specifying Property Types

    This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

    When you want to specify an NHibernate type (implementation of NHibernate.Type.IType) for a property using mapping by code, you might be tempted to do something like this:

       1: ca.Property(x => x.SomeProperty, x =>
       2: {
       3:     x.Type<StringClobType>();
       4: });

    If you try this, you will get a nasty exception about the type not having a public parameterless constructor. Ouch!

    The thing is: NHibernate types are supposed to be stateless, and therefore it does not make sense to have several instances of a type, that is why NHibernate encourages us to use the static fields defined in the NHibernateUtil class and that is also why most of the built-in types don’t have a public parameterless constructor.

    So, if you want to implement your own types, you can certainly add a public parameterless constructor to them and use the above syntax, but if you are going to use the built-in types, you should instead use:

       1: ca.Property(x => x.SomeProperty, x =>
       2: {
       3:     x.Type(NHibernateUtil.StringClob);
       4: });

    Read more...