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!

                             

No Comments

Add a Comment

As it will appear on the website

Not displayed

Your website