Contents tagged with LINQ

  • 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...

  • Entity Framework Pitfalls – Registering Custom Database Functions for LINQ

    Like I said in my previous post, it’s not enough to add a DbFunctionAttribute attribute to a method to have it call a database function. If the function is not a built-in one, it will not be registered in the Entity Framework provider manifest for SQL Server, so it will require registration in the model. You might think, just by looking at the methods in SqlFunctions that all that it took was to add this attribute, but you would be wrong.

    Read more...

  • Registering SQL Server Built-in Functions to Entity Framework Code First

    It is possible to register custom functions that exist in the database so that they can be called by Entity Framework Code First LINQ queries.

    For example, consider the SOUNDEX function (yes, I know, I know, I always give SOUNDEX as an example for LINQ extensions! Winking smile). We could write it in C# as this:

       1: public static String Soundex(this String input)
       2: {
       3:     const String values = "01230120022455012623010202";
       4:     const Int32 encodingLength = 4;
       5:  
       6:     var prevChar = ' ';
       7:  
       8:     input = Normalize(input);
       9:  
      10:     if (input.Length == 0)
      11:     {
      12:         return (input);
      13:     }
      14:  
      15:     var builder = new StringBuilder(input[0]);
      16:  
      17:     for (var i = 1; ((i < input.Length) && (builder.Length < encodingLength)); ++i)
      18:     {
      19:         var c = values[input[i] - 'A'];
      20:  
      21:         if ((c != '0') && (c != prevChar))
      22:         {
      23:             builder.Append(c);
      24:             prevChar = c;
      25:         }
      26:     }
      27:  
      28:     while (builder.Length < encodingLength)
      29:     {
      30:         builder.Append('0');
      31:     }
      32:  
      33:     return (builder.ToString());
      34: }
      35:  
      36: private static String Normalize(String text)
      37: {
      38:     var builder = new StringBuilder();
      39:  
      40:     foreach (var c in text)
      41:     {
      42:         if (Char.IsLetter(c) == true)
      43:         {
      44:             builder.Append(Char.ToUpper(c));
      45:         }
      46:     }
      47:  
      48:     return (builder.ToString());
      49: }

    If we want this method to be callable by a LINQ query, we need to add the DbFunctionAttribute to it, specifying the name of the database function we wish to call, because the .NET method and the database function names can be different:

       1: [DbFunction("SqlServer", "SOUNDEX")]
       2: public static String Soundex(this String input)
       3: {
       4:     //...
       5: }

    And for calling it:

       1: var soundexNames = ctx.Projects.Select(p => p.Name.Soundex()).ToList();

    However, for certain database functions, it requires a bit more work to get done. Let us consider now the FORMAT function and a .NET implementation:

       1: public static String Format(this DateTime value, String format, String culture)
       2: {
       3:     return (value.ToString(format, CultureInfo.CreateSpecificCulture(culture)));
       4: }

    Besides adding the DbFunctionAttribute attribute:

       1: [DbFunction("CodeFirstDatabaseSchema", "FORMAT")]
       2: public static String Format(this DateTime value, String format, String culture)
       3: {
       4:     //...
       5: }

    it also requires that we register it explicitly in our model, for that, we override the OnModelCreating method and add a custom convention:

       1: protected override void OnModelCreating(DbModelBuilder modelBuilder)
       2: {
       3:     modelBuilder.Conventions.Add(new RegisterFunctionConvention());
       4:  
       5:     base.OnModelCreating(modelBuilder);
       6: }

    The convention being:

       1: public class RegisterFunctionConvention : IStoreModelConvention<EdmModel>
       2: {
       3:     public void Apply(EdmModel item, DbModel model)
       4:     {
       5:         var valueParameter = FunctionParameter.Create("value", this.GetStorePrimitiveType(model, PrimitiveTypeKind.DateTime), ParameterMode.In);
       6:         var formatParameter = FunctionParameter.Create("format", this.GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In);
       7:         var cultureParameter = FunctionParameter.Create("culture", this.GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In);
       8:         var returnValue = FunctionParameter.Create("result", this.GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.ReturnValue);
       9:  
      10:         var function = this.CreateAndAddFunction(item, "FORMAT", new[] { valueParameter, formatParameter, cultureParameter }, new[] { returnValue });
      11:     }
      12:  
      13:     protected EdmFunction CreateAndAddFunction(EdmModel item, String name, IList<FunctionParameter> parameters, IList<FunctionParameter> returnValues)
      14:     {
      15:         var payload = new EdmFunctionPayload { StoreFunctionName = name, Parameters = parameters, ReturnParameters = returnValues, Schema = this.GetDefaultSchema(item), IsBuiltIn = true };
      16:         var function = EdmFunction.Create(name, this.GetDefaultNamespace(item), item.DataSpace, payload, null);
      17:  
      18:         item.AddItem(function);
      19:  
      20:         return (function);
      21:     }
      22:  
      23:     protected EdmType GetStorePrimitiveType(DbModel model, PrimitiveTypeKind typeKind)
      24:     {
      25:         return (model.ProviderManifest.GetStoreType(TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(typeKind))).EdmType);
      26:     }
      27:  
      28:     protected String GetDefaultNamespace(EdmModel layerModel)
      29:     {
      30:         return (layerModel.GlobalItems.OfType<EdmType>().Select(t => t.NamespaceName).Distinct().Single());
      31:     }
      32:  
      33:     protected String GetDefaultSchema(EdmModel layerModel)
      34:     {
      35:         return (layerModel.Container.EntitySets.Select(s => s.Schema).Distinct().SingleOrDefault());
      36:     }
      37: }

    I got some of the metadata code from Diego Vega’s (@divega) repository: https://github.com/divega/, but changed it slightly.

    Now we have the FORMAT function available to LINQ:

       1: var projectDates = ctx.Projects.Select(p => p.Start.Format("D", "pt-PT")).ToList();

    Now, I hear you ask: why for SOUNDEX we just need to add a simple attribute and for FORMAT we need so much more? Well, it just happens that SOUNDEX is defined in the Entity Framework SQL Server provider manifest - see it here. All of the functions in SqlFunctions are present in the manifest, but the opposite is not true - not all functions in the manifest are in SqlFunctions, but that's the way it is! Thanks to @divega for the explanation.

    Some things worth mentioning:

    • The namespace and name used in the DbFunctionAttribute attribute must match those passed to EdmFunction.Create (CodeFirstDatabaseSchema is what GetDefaultNamespace returns;
    • You cannot specify two functions with the same name and different parameters.

    There is an open request to add the FORMAT function to the list of functions supported out of the box by Entity Framework Code First: https://entityframework.codeplex.com/workitem/2586 through the SqlFunctions class, but in the meantime, this might be useful!

    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...

  • Mapping Non-Public Members With Entity Framework Code First

    This is a common request, and really makes sense; we need to use LINQ expressions and a bit of reflection magic. First, an helper function for returning an expression that points to a member:

       1: public static class ExpressionHelper
       2: {
       3:     public static Expression<Func<TEntity, TResult>> GetMember<TEntity, TResult>(String memberName)
       4:     {
       5:         ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "p");
       6:         MemberExpression member = Expression.MakeMemberAccess(parameter, typeof(TEntity).GetMember(memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Single());
       7:         Expression<Func<TEntity, TResult>> expression = Expression.Lambda<Func<TEntity, TResult>>(member, parameter);
       8:         return (expression);
       9:     }
      10: }

    Then, we call it on the DbContext.OnModelCreating method, as a parameter to StructuralTypeConfiguration<TStructuralType>.Property:

       1: protected override void OnModelCreating(DbModelBuilder modelBuilder)
       2: {
       3:     modelBuilder.Entity<Project>().Property(ExpressionHelper.GetMember<Project, Decimal>("Budget")).IsRequired();
       4:  
       5:     base.OnModelCreating(modelBuilder);
       6: }
    And that’s it!

    Read more...

  • Caching LINQ Queries

    Introduction

    Some days ago I wrote a post on comparing LINQ expressions where I shared my discoveries and general discontent on how difficult it is do right. The reason I was looking into it was because I wanted to write a LINQ query caching mechanism.

    There are actually three main problems involved:

    • Comparing two LINQ expressions;
    • Caching a LINQ expression for some time;
    • For cached instances, preventing them from executing when they are enumerated after the first time.

    Comparing LINQ Expressions

    I ended up writing my own comparer, which involved looking at all possible types of Expressions (around 20 classes). For some it is quite easy, because they have few relevant properties – take ConstantExpression, for instance, I only had to consider the Value property – while for others there are a few – BinaryExpression has Left, Right, Conversion and Method. OK, so here is the code:

       1: public sealed class ExpressionEqualityComparer : IEqualityComparer<Expression>
       2: {
       3:     #region Private fields
       4:     private Int32 hashCode;
       5:     #endregion
       6:  
       7:     #region Hash code
       8:     private void Visit(Expression expression)
       9:     {
      10:         if (expression == null)
      11:         {
      12:             return;
      13:         }
      14:  
      15:         this.hashCode ^= (Int32) expression.NodeType ^ expression.Type.GetHashCode();
      16:  
      17:         switch (expression.NodeType)
      18:         {
      19:             case ExpressionType.ArrayLength:
      20:             case ExpressionType.Convert:
      21:             case ExpressionType.ConvertChecked:
      22:             case ExpressionType.Negate:
      23:             case ExpressionType.UnaryPlus:
      24:             case ExpressionType.NegateChecked:
      25:             case ExpressionType.Not:
      26:             case ExpressionType.Quote:
      27:             case ExpressionType.TypeAs:
      28:                 this.VisitUnary((UnaryExpression) expression);
      29:                 break;
      30:  
      31:             case ExpressionType.Add:
      32:             case ExpressionType.AddChecked:
      33:             case ExpressionType.And:
      34:             case ExpressionType.AndAlso:
      35:             case ExpressionType.ArrayIndex:
      36:             case ExpressionType.Coalesce:
      37:             case ExpressionType.Divide:
      38:             case ExpressionType.Equal:
      39:             case ExpressionType.ExclusiveOr:
      40:             case ExpressionType.GreaterThan:
      41:             case ExpressionType.GreaterThanOrEqual:
      42:             case ExpressionType.LeftShift:
      43:             case ExpressionType.LessThan:
      44:             case ExpressionType.LessThanOrEqual:
      45:             case ExpressionType.Modulo:
      46:             case ExpressionType.Multiply:
      47:             case ExpressionType.MultiplyChecked:
      48:             case ExpressionType.NotEqual:
      49:             case ExpressionType.Or:
      50:             case ExpressionType.OrElse:
      51:             case ExpressionType.Power:
      52:             case ExpressionType.RightShift:
      53:             case ExpressionType.Subtract:
      54:             case ExpressionType.SubtractChecked:
      55:                 this.VisitBinary((BinaryExpression) expression);
      56:                 break;
      57:  
      58:             case ExpressionType.Call:
      59:                 this.VisitMethodCall((MethodCallExpression) expression);
      60:                 break;
      61:  
      62:             case ExpressionType.Conditional:
      63:                 this.VisitConditional((ConditionalExpression) expression);
      64:                 break;
      65:  
      66:             case ExpressionType.Constant:
      67:                 this.VisitConstant((ConstantExpression) expression);
      68:                 break;
      69:  
      70:             case ExpressionType.Invoke:
      71:                 this.VisitInvocation((InvocationExpression) expression);
      72:                 break;
      73:  
      74:             case ExpressionType.Lambda:
      75:                 this.VisitLambda((LambdaExpression) expression);
      76:                 break;
      77:  
      78:             case ExpressionType.ListInit:
      79:                 this.VisitListInit((ListInitExpression) expression);
      80:                 break;
      81:  
      82:             case ExpressionType.MemberAccess:
      83:                 this.VisitMemberAccess((MemberExpression) expression);
      84:                 break;
      85:  
      86:             case ExpressionType.MemberInit:
      87:                 this.VisitMemberInit((MemberInitExpression) expression);
      88:                 break;
      89:  
      90:             case ExpressionType.New:
      91:                 this.VisitNew((NewExpression) expression);
      92:                 break;
      93:  
      94:             case ExpressionType.NewArrayInit:
      95:             case ExpressionType.NewArrayBounds:
      96:                 this.VisitNewArray((NewArrayExpression) expression);
      97:                 break;
      98:  
      99:             case ExpressionType.Parameter:
     100:                 this.VisitParameter((ParameterExpression) expression);
     101:                 break;
     102:  
     103:             case ExpressionType.TypeIs:
     104:                 this.VisitTypeIs((TypeBinaryExpression) expression);
     105:                 break;
     106:  
     107:             default:
     108:                 throw (new ArgumentException("Unhandled expression type"));
     109:         }
     110:     }
     111:  
     112:     private void VisitUnary(UnaryExpression expression)
     113:     {
     114:         if (expression.Method != null)
     115:         {
     116:             this.hashCode ^= expression.Method.GetHashCode();
     117:         }
     118:  
     119:         this.Visit(expression.Operand);
     120:     }
     121:  
     122:     private void VisitBinary(BinaryExpression expression)
     123:     {
     124:         if (expression.Method != null)
     125:         {
     126:             this.hashCode ^= expression.Method.GetHashCode();
     127:         }
     128:  
     129:         this.Visit(expression.Left);
     130:         this.Visit(expression.Right);
     131:         this.Visit(expression.Conversion);
     132:     }
     133:  
     134:     private void VisitMethodCall(MethodCallExpression expression)
     135:     {
     136:         this.hashCode ^= expression.Method.GetHashCode();
     137:  
     138:         this.Visit(expression.Object);
     139:         this.VisitExpressionList(expression.Arguments);
     140:     }
     141:  
     142:     private void VisitConditional(ConditionalExpression expression)
     143:     {
     144:         this.Visit(expression.Test);
     145:         this.Visit(expression.IfTrue);
     146:         this.Visit(expression.IfFalse);
     147:     }
     148:  
     149:     private void VisitConstant(ConstantExpression expression)
     150:     {
     151:         if (expression.Value != null)
     152:         {
     153:             this.hashCode ^= expression.Value.GetHashCode();
     154:         }
     155:     }
     156:  
     157:     private void VisitInvocation(InvocationExpression expression)
     158:     {
     159:         this.Visit(expression.Expression);
     160:         this.VisitExpressionList(expression.Arguments);
     161:     }
     162:  
     163:     private void VisitLambda(LambdaExpression expression)
     164:     {
     165:         if (expression.Name != null)
     166:         {
     167:             this.hashCode ^= expression.Name.GetHashCode();
     168:         }
     169:  
     170:         this.Visit(expression.Body);
     171:         this.VisitParameterList(expression.Parameters);
     172:     }
     173:  
     174:     private void VisitListInit(ListInitExpression expression)
     175:     {
     176:         this.VisitNew(expression.NewExpression);
     177:         this.VisitElementInitializerList(expression.Initializers);
     178:     }
     179:  
     180:     private void VisitMemberAccess(MemberExpression expression)
     181:     {
     182:         this.hashCode ^= expression.Member.GetHashCode();
     183:         this.Visit(expression.Expression);
     184:     }
     185:  
     186:     private void VisitMemberInit(MemberInitExpression expression)
     187:     {
     188:         this.Visit(expression.NewExpression);
     189:         this.VisitBindingList(expression.Bindings);
     190:     }
     191:  
     192:     private void VisitNew(NewExpression expression)
     193:     {
     194:         this.hashCode ^= expression.Constructor.GetHashCode();
     195:  
     196:         this.VisitMemberList(expression.Members);
     197:         this.VisitExpressionList(expression.Arguments);
     198:     }
     199:  
     200:     private void VisitNewArray(NewArrayExpression expression)
     201:     {
     202:         this.VisitExpressionList(expression.Expressions);
     203:     }
     204:  
     205:     private void VisitParameter(ParameterExpression expression)
     206:     {
     207:         if (expression.Name != null)
     208:         {
     209:             this.hashCode ^= expression.Name.GetHashCode();
     210:         }
     211:     }
     212:  
     213:     private void VisitTypeIs(TypeBinaryExpression expression)
     214:     {
     215:         this.hashCode ^= expression.TypeOperand.GetHashCode();
     216:         this.Visit(expression.Expression);
     217:     }
     218:  
     219:     private void VisitBinding(MemberBinding binding)
     220:     {
     221:         this.hashCode ^= (Int32)binding.BindingType ^ binding.Member.GetHashCode();
     222:  
     223:         switch (binding.BindingType)
     224:         {
     225:             case MemberBindingType.Assignment:
     226:                 this.VisitMemberAssignment((MemberAssignment)binding);
     227:                 break;
     228:  
     229:             case MemberBindingType.MemberBinding:
     230:                 this.VisitMemberMemberBinding((MemberMemberBinding)binding);
     231:                 break;
     232:  
     233:             case MemberBindingType.ListBinding:
     234:                 this.VisitMemberListBinding((MemberListBinding)binding);
     235:                 break;
     236:             
     237:             default:
     238:                 throw (new ArgumentException("Unhandled binding type"));
     239:         }
     240:     }
     241:  
     242:     private void VisitMemberAssignment(MemberAssignment assignment)
     243:     {
     244:         this.Visit(assignment.Expression);
     245:     }
     246:  
     247:     private void VisitMemberMemberBinding(MemberMemberBinding binding)
     248:     {
     249:         this.VisitBindingList(binding.Bindings);
     250:     }
     251:  
     252:     private void VisitMemberListBinding(MemberListBinding binding)
     253:     {
     254:         this.VisitElementInitializerList(binding.Initializers);
     255:     }
     256:  
     257:     private void VisitElementInitializer(ElementInit initializer)
     258:     {
     259:         this.hashCode ^= initializer.AddMethod.GetHashCode();
     260:  
     261:         this.VisitExpressionList(initializer.Arguments);
     262:     }
     263:  
     264:     private void VisitExpressionList(ReadOnlyCollection<Expression> list)
     265:     {
     266:         if (list != null)
     267:         {
     268:             for (Int32 i = 0; i < list.Count; i++)
     269:             {
     270:                 this.Visit(list[i]);
     271:             }
     272:         }
     273:     }
     274:  
     275:     private void VisitParameterList(ReadOnlyCollection<ParameterExpression> list)
     276:     {
     277:         if (list != null)
     278:         {
     279:             for (Int32 i = 0; i < list.Count; i++)
     280:             {
     281:                 this.Visit(list[i]);
     282:             }
     283:         }
     284:     }
     285:  
     286:     private void VisitBindingList(ReadOnlyCollection<MemberBinding> list)
     287:     {
     288:         if (list != null)
     289:         {
     290:             for (Int32 i = 0; i < list.Count; i++)
     291:             {
     292:                 this.VisitBinding(list[i]);
     293:             }
     294:         }
     295:     }
     296:  
     297:     private void VisitElementInitializerList(ReadOnlyCollection<ElementInit> list)
     298:     {
     299:         if (list != null)
     300:         {
     301:             for (Int32 i = 0; i < list.Count; i++)
     302:             {
     303:                 this.VisitElementInitializer(list[i]);
     304:             }
     305:         }
     306:     }
     307:  
     308:     private void VisitMemberList(ReadOnlyCollection<MemberInfo> list)
     309:     {
     310:         if (list != null)
     311:         {
     312:             for (Int32 i = 0; i < list.Count; i++)
     313:             {
     314:                 this.hashCode ^= list[i].GetHashCode();
     315:             }
     316:         }
     317:     }
     318:     #endregion
     319:  
     320:     #region Equality
     321:     private Boolean Visit(Expression x, Expression y)
     322:     {
     323:         if (Object.ReferenceEquals(x, y) == true)
     324:         {
     325:             return (true);
     326:         }
     327:  
     328:         if ((x == null) || (y == null))
     329:         {
     330:             return (false);
     331:         }
     332:  
     333:         if ((x.NodeType != y.NodeType) || (x.Type != y.Type))
     334:         {
     335:             return (false);
     336:         }
     337:  
     338:         switch (x.NodeType)
     339:         {
     340:             case ExpressionType.ArrayLength:
     341:             case ExpressionType.Convert:
     342:             case ExpressionType.ConvertChecked:
     343:             case ExpressionType.Negate:
     344:             case ExpressionType.UnaryPlus:
     345:             case ExpressionType.NegateChecked:
     346:             case ExpressionType.Not:
     347:             case ExpressionType.Quote:
     348:             case ExpressionType.TypeAs:
     349:                 return (this.VisitUnary((UnaryExpression)x, (UnaryExpression)y));
     350:  
     351:             case ExpressionType.Add:
     352:             case ExpressionType.AddChecked:
     353:             case ExpressionType.And:
     354:             case ExpressionType.AndAlso:
     355:             case ExpressionType.ArrayIndex:
     356:             case ExpressionType.Coalesce:
     357:             case ExpressionType.Divide:
     358:             case ExpressionType.Equal:
     359:             case ExpressionType.ExclusiveOr:
     360:             case ExpressionType.GreaterThan:
     361:             case ExpressionType.GreaterThanOrEqual:
     362:             case ExpressionType.LeftShift:
     363:             case ExpressionType.LessThan:
     364:             case ExpressionType.LessThanOrEqual:
     365:             case ExpressionType.Modulo:
     366:             case ExpressionType.Multiply:
     367:             case ExpressionType.MultiplyChecked:
     368:             case ExpressionType.NotEqual:
     369:             case ExpressionType.Or:
     370:             case ExpressionType.OrElse:
     371:             case ExpressionType.Power:
     372:             case ExpressionType.RightShift:
     373:             case ExpressionType.Subtract:
     374:             case ExpressionType.SubtractChecked:
     375:                 return (this.VisitBinary((BinaryExpression)x, (BinaryExpression)y));
     376:  
     377:             case ExpressionType.Call:
     378:                 return (this.VisitMethodCall((MethodCallExpression)x, (MethodCallExpression)y));
     379:  
     380:             case ExpressionType.Conditional:
     381:                 return (this.VisitConditional((ConditionalExpression)x, (ConditionalExpression)y));
     382:  
     383:             case ExpressionType.Constant:
     384:                 return (this.VisitConstant((ConstantExpression)x, (ConstantExpression)y));
     385:  
     386:             case ExpressionType.Invoke:
     387:                 return (this.VisitInvocation((InvocationExpression)x, (InvocationExpression)y));
     388:  
     389:             case ExpressionType.Lambda:
     390:                 return (this.VisitLambda((LambdaExpression)x, (LambdaExpression)y));
     391:  
     392:             case ExpressionType.ListInit:
     393:                 return (this.VisitListInit((ListInitExpression)x, (ListInitExpression)y));
     394:  
     395:             case ExpressionType.MemberAccess:
     396:                 return (this.VisitMemberAccess((MemberExpression)x, (MemberExpression)y));
     397:  
     398:             case ExpressionType.MemberInit:
     399:                 return (this.VisitMemberInit((MemberInitExpression)x, (MemberInitExpression)y));
     400:  
     401:             case ExpressionType.New:
     402:                 return (this.VisitNew((NewExpression)x, (NewExpression)y));
     403:  
     404:             case ExpressionType.NewArrayInit:
     405:             case ExpressionType.NewArrayBounds:
     406:                 return (this.VisitNewArray((NewArrayExpression)x, (NewArrayExpression)y));
     407:  
     408:             case ExpressionType.Parameter:
     409:                 return (this.VisitParameter((ParameterExpression)x, (ParameterExpression)y));
     410:  
     411:             case ExpressionType.TypeIs:
     412:                 return (this.VisitTypeIs((TypeBinaryExpression)x, (TypeBinaryExpression)y));
     413:  
     414:             default:
     415:                 throw (new ArgumentException("Unhandled expression type"));
     416:         }
     417:     }
     418:  
     419:     private Boolean VisitUnary(UnaryExpression x, UnaryExpression y)
     420:     {
     421:         return ((x.Method == y.Method) &&
     422:                (this.Visit(x.Operand, y.Operand)));
     423:     }
     424:  
     425:     private Boolean VisitBinary(BinaryExpression x, BinaryExpression y)
     426:     {
     427:         return ((x.Method == y.Method) &&
     428:                (this.Visit(x.Left, y.Left)) &&
     429:                (this.Visit(x.Right, y.Right)) &&
     430:                (this.Visit(x.Conversion, y.Conversion)));
     431:     }
     432:  
     433:     private Boolean VisitMethodCall(MethodCallExpression x, MethodCallExpression y)
     434:     {
     435:         return ((x.Method == y.Method) &&
     436:                (this.Visit(x.Object, y.Object)) &&
     437:                (this.VisitExpressionList(x.Arguments, y.Arguments)));
     438:     }
     439:  
     440:     private Boolean VisitConditional(ConditionalExpression x, ConditionalExpression y)
     441:     {
     442:         return ((this.Visit(x.Test, y.Test)) &&
     443:                (this.Visit(x.IfTrue, y.IfTrue)) &&
     444:                (this.Visit(x.IfFalse, y.IfFalse)));
     445:     }
     446:  
     447:     private Boolean VisitConstant(ConstantExpression x, ConstantExpression y)
     448:     {
     449:         return (Object.Equals(x.Value, y.Value));
     450:     }
     451:  
     452:     private Boolean VisitInvocation(InvocationExpression x, InvocationExpression y)
     453:     {
     454:         return ((this.Visit(x.Expression, y.Expression)) &&
     455:                (this.VisitExpressionList(x.Arguments, x.Arguments)));
     456:     }
     457:  
     458:     private Boolean VisitLambda(LambdaExpression x, LambdaExpression y)
     459:     {
     460:         return ((this.Visit(x.Body, y.Body)) &&
     461:                (this.VisitParameterList(x.Parameters, y.Parameters)));
     462:     }
     463:  
     464:     private Boolean VisitListInit(ListInitExpression x, ListInitExpression y)
     465:     {
     466:         return ((this.VisitNew(x.NewExpression, y.NewExpression)) &&
     467:                (this.VisitElementInitializerList(x.Initializers, y.Initializers)));
     468:     }
     469:  
     470:     private Boolean VisitMemberAccess(MemberExpression x, MemberExpression y)
     471:     {
     472:         return ((x.Member == y.Member) &&
     473:                (this.Visit(x.Expression, y.Expression)));
     474:     }
     475:  
     476:     private Boolean VisitMemberInit(MemberInitExpression x, MemberInitExpression y)
     477:     {
     478:         return ((this.Visit(x.NewExpression, y.NewExpression)) &&
     479:                (this.VisitBindingList(x.Bindings, y.Bindings)));
     480:     }
     481:  
     482:     private Boolean VisitNew(NewExpression x, NewExpression y)
     483:     {
     484:         return ((x.Constructor == y.Constructor) &&
     485:                (this.VisitMemberList(x.Members, y.Members)) &&
     486:                (this.VisitExpressionList(x.Arguments, y.Arguments)));
     487:     }
     488:  
     489:     private Boolean VisitNewArray(NewArrayExpression x, NewArrayExpression y)
     490:     {
     491:         return (this.VisitExpressionList(x.Expressions, y.Expressions));
     492:     }
     493:  
     494:     private Boolean VisitParameter(ParameterExpression x, ParameterExpression y)
     495:     {
     496:         return ((x.Type == y.Type) && (x.IsByRef == y.IsByRef));
     497:     }
     498:  
     499:     private Boolean VisitTypeIs(TypeBinaryExpression x, TypeBinaryExpression y)
     500:     {
     501:         return ((x.TypeOperand == y.TypeOperand) &&
     502:                (this.Visit(x.Expression, y.Expression)));
     503:     }
     504:  
     505:     private Boolean VisitBinding(MemberBinding x, MemberBinding y)
     506:     {
     507:         if ((x.BindingType != y.BindingType) || (x.Member != y.Member))
     508:         {
     509:             return (false);
     510:         }
     511:  
     512:         switch (x.BindingType)
     513:         {
     514:             case MemberBindingType.Assignment:
     515:                 return (this.VisitMemberAssignment((MemberAssignment)x, (MemberAssignment)y));
     516:  
     517:             case MemberBindingType.MemberBinding:
     518:                 return (this.VisitMemberMemberBinding((MemberMemberBinding)x, (MemberMemberBinding)y));
     519:  
     520:             case MemberBindingType.ListBinding:
     521:                 return (this.VisitMemberListBinding((MemberListBinding)x, (MemberListBinding)y));
     522:  
     523:             default:
     524:                 throw (new ArgumentException("Unhandled binding type"));
     525:         }
     526:     }
     527:  
     528:     private Boolean VisitMemberAssignment(MemberAssignment x, MemberAssignment y)
     529:     {
     530:         return (this.Visit(x.Expression, y.Expression));
     531:     }
     532:  
     533:     private Boolean VisitMemberMemberBinding(MemberMemberBinding x, MemberMemberBinding y)
     534:     {
     535:         return (this.VisitBindingList(x.Bindings, y.Bindings));
     536:     }
     537:  
     538:     private Boolean VisitMemberListBinding(MemberListBinding x, MemberListBinding y)
     539:     {
     540:         return (this.VisitElementInitializerList(x.Initializers, y.Initializers));
     541:     }
     542:  
     543:     private Boolean VisitElementInitializer(ElementInit x, ElementInit y)
     544:     {
     545:         return ((x.AddMethod == y.AddMethod) &&
     546:                (this.VisitExpressionList(x.Arguments, y.Arguments)));
     547:     }
     548:  
     549:     private Boolean VisitExpressionList(ReadOnlyCollection<Expression> x, ReadOnlyCollection<Expression> y)
     550:     {
     551:         if (x == y)
     552:         {
     553:             return (true);
     554:         }
     555:  
     556:         if ((x != null) && (y != null) && (x.Count == y.Count))
     557:         {
     558:             for (Int32 i = 0; i < x.Count; i++)
     559:             {
     560:                 if (this.Visit(x[i], y[i]) == false)
     561:                 {
     562:                     return (false);
     563:                 }
     564:             }
     565:  
     566:             return (true);
     567:         }
     568:  
     569:         return (false);
     570:     }
     571:  
     572:     private Boolean VisitParameterList(ReadOnlyCollection<ParameterExpression> x, ReadOnlyCollection<ParameterExpression> y)
     573:     {
     574:         if (x == y)
     575:         {
     576:             return (true);
     577:         }
     578:  
     579:         if ((x != null) && (y != null) && (x.Count == y.Count))
     580:         {
     581:             for (Int32 i = 0; i < x.Count; i++)
     582:             {
     583:                 if (this.Visit(x[i], y[i]) == false)
     584:                 {
     585:                     return (false);
     586:                 }
     587:             }
     588:  
     589:             return (true);
     590:         }
     591:  
     592:         return (false);
     593:     }
     594:  
     595:     private Boolean VisitBindingList(ReadOnlyCollection<MemberBinding> x, ReadOnlyCollection<MemberBinding> y)
     596:     {
     597:         if (x == y)
     598:         {
     599:             return (true);
     600:         }
     601:  
     602:         if ((x != null) && (y != null) && (x.Count == y.Count))
     603:         {
     604:             for (Int32 i = 0; i < x.Count; i++)
     605:             {
     606:                 if (this.VisitBinding(x[i], y[i]) == false)
     607:                 {
     608:                     return (false);
     609:                 }
     610:             }
     611:  
     612:             return (true);
     613:         }
     614:  
     615:         return (false);
     616:     }
     617:  
     618:     private Boolean VisitElementInitializerList(ReadOnlyCollection<ElementInit> x, ReadOnlyCollection<ElementInit> y)
     619:     {
     620:         if (x == y)
     621:         {
     622:             return (true);
     623:         }
     624:  
     625:         if ((x != null) && (y != null) && (x.Count == y.Count))
     626:         {
     627:             for (Int32 i = 0; i < x.Count; i++)
     628:             {
     629:                 if (this.VisitElementInitializer(x[i], y[i]) == false)
     630:                 {
     631:                     return (false);
     632:                 }
     633:             }
     634:  
     635:             return (true);
     636:         }
     637:  
     638:         return (false);
     639:     }
     640:  
     641:     private Boolean VisitMemberList(ReadOnlyCollection<MemberInfo> x, ReadOnlyCollection<MemberInfo> y)
     642:     {
     643:         if (x == y)
     644:         {
     645:             return (true);
     646:         }
     647:  
     648:         if ((x != null) && (y != null) && (x.Count == y.Count))
     649:         {
     650:             for (Int32 i = 0; i < x.Count; i++)
     651:             {
     652:                 if (x[i] != y[i])
     653:                 {
     654:                     return (false);
     655:                 }
     656:             }
     657:  
     658:             return (true);
     659:         }
     660:  
     661:         return (false);
     662:     }
     663:     #endregion
     664:  
     665:     #region IEqualityComparer<Expression> Members
     666:     public Boolean Equals(Expression x, Expression y)
     667:     {
     668:         return (this.Visit(x, y));
     669:     }
     670:  
     671:     public Int32 GetHashCode(Expression expression)
     672:     {
     673:         this.hashCode = 0;
     674:  
     675:         this.Visit(expression);
     676:  
     677:         return (this.hashCode);
     678:     }
     679:     #endregion
     680: }

    This implementation disregards the lambda variable name, so that “x => …” is equal to “y => …”. Also, as you can see, this is not safe for multithreaded usage, because it uses an accumulator field (hashCode), where the hash for the expression currently being calculated is stored. Being an IEqualityComparer<Expression>, it implements both Equals and GetHashCode methods.

    Caching

    As for caching, I decided to use the MemoryCache implementation of an ObjectCache, available on .NET 4, this way I don’t have any external dependencies:

       1: public static class QueryableExtensions
       2: {
       3:     public static IQueryable<T> AsCacheable<T>(this IQueryable<T> queryable, TimeSpan duration)
       4:     {
       5:         return (AsCacheable(queryable, (Int32) duration.TotalSeconds));
       6:     }
       7:  
       8:     public static IQueryable<T> AsCacheable<T>(this IQueryable<T> queryable, Int32 durationSeconds)
       9:     {
      10:         ObjectCache cache = null;
      11:  
      12:         if (ObjectCache.Host != null)
      13:         {
      14:             cache = ObjectCache.Host.GetService(typeof(ObjectCache)) as ObjectCache;
      15:         }
      16:  
      17:         cache = cache ?? MemoryCache.Default;
      18:  
      19:         IQueryable<T> cachedQuery = new QueryableWrapper<T>(cache, queryable, durationSeconds);
      20:  
      21:         return (cachedQuery);
      22:     }
      23:  
      24:     public static IOrderedQueryable<T> AsCacheable<T>(this IOrderedQueryable<T> queryable, TimeSpan duration)
      25:     {
      26:         return (AsCacheable(queryable as IQueryable<T>, duration) as IOrderedQueryable<T>);
      27:     }
      28:  
      29:     public static IOrderedQueryable<T> AsCacheable<T>(this IOrderedQueryable<T> queryable, Int32 durationSeconds)
      30:     {
      31:         return (AsCacheable(queryable as IQueryable<T>, durationSeconds) as IOrderedQueryable<T>);
      32:     }
      33: }

    As you can see, we are free to supply out own ObjectCache implementation, provided we place a IServiceProvider implementation (UnityServiceLocator will do) on the ObjectCache.Host property and this implementation returns a valid ObjectCache instance. Feel free to replace this by any other similar mechanism!

    Preventing Multiple Query Executions

    So, when an IQueryable<T> is first executed, it will go to the database, or someplace else (just think WCF Data Services’ DataServiceQuery<T>), and return its results. If we are going to put that query in a cache, we want to prevent it from executing multiple times, otherwise the purpose of the cache would be defaced. For that, I built my own class that just inherits from IQueryable<T> (actually, from IOrderedQueryable<T>, for support of ordered queries) and overrides the IEnumerable<T> (of which IQueryable<T> descends) GetEnumerator method:

       1: sealed class QueryableWrapper<T> : IOrderedQueryable<T>
       2: {
       3:     private static readonly ExpressionEqualityComparer comparer = new ExpressionEqualityComparer();
       4:  
       5:     sealed class EnumeratorWrapper : IEnumerator<T>
       6:     {
       7:         private readonly LinkedList<T> list = new LinkedList<T>();
       8:         private QueryableWrapper<T> queryable;
       9:         private IEnumerator<T> enumerator;
      10:         private Boolean stored = false;
      11:         internal Boolean consumed;
      12:  
      13:         public EnumeratorWrapper(QueryableWrapper<T> queryable, IEnumerator<T> enumerator)
      14:         {
      15:             this.enumerator = enumerator;
      16:             this.queryable = queryable;
      17:         }
      18:  
      19:         internal IEnumerator<T> FromCache()
      20:         {
      21:             return (this.list.GetEnumerator());
      22:         }
      23:  
      24:         #region IEnumerator<T> Members
      25:  
      26:         public T Current
      27:         {
      28:             get
      29:             {
      30:                 T current = this.enumerator.Current;
      31:  
      32:                 if (this.stored == false)
      33:                 {
      34:                     this.list.AddLast(current);
      35:                     this.stored = true;
      36:                 }
      37:  
      38:                 return (current);
      39:             }
      40:         }
      41:  
      42:         #endregion
      43:  
      44:         #region IDisposable Members
      45:  
      46:         public void Dispose()
      47:         {
      48:             this.stored = false;
      49:             this.consumed = true;
      50:             this.enumerator.Dispose();
      51:         }
      52:  
      53:         #endregion
      54:  
      55:         #region IEnumerator Members
      56:  
      57:         Object IEnumerator.Current
      58:         {
      59:             get
      60:             {
      61:                 return (this.Current);
      62:             }
      63:         }
      64:  
      65:         public Boolean MoveNext()
      66:         {
      67:             Boolean result = this.enumerator.MoveNext();
      68:  
      69:             if (result == true)
      70:             {
      71:                 this.stored = false;
      72:             }
      73:  
      74:             return (result);
      75:         }
      76:  
      77:         public void Reset()
      78:         {
      79:             this.stored = false;
      80:             this.list.Clear();
      81:             this.enumerator.Reset();
      82:         }
      83:  
      84:         #endregion
      85:     }
      86:  
      87:     #region Private readonly fields
      88:     private readonly IQueryable<T> queryable;
      89:     private readonly ObjectCache cache;
      90:     private readonly Int32 durationSeconds;
      91:     #endregion
      92:  
      93:     #region Internal constructor
      94:     internal QueryableWrapper(ObjectCache cache, IQueryable<T> queryable, Int32 durationSeconds)
      95:     {
      96:         this.cache = cache;
      97:         this.queryable = queryable;
      98:         this.durationSeconds = durationSeconds;
      99:     }
     100:     #endregion
     101:  
     102:     #region IEnumerable<T> Members
     103:  
     104:     public IEnumerator<T> GetEnumerator()
     105:     {
     106:         IEnumerator<T> enumerator = null;
     107:         String key = this.GetKey(this.queryable).ToString();
     108:  
     109:         if (this.cache.Contains(key) == true)
     110:         {
     111:             //hit
     112:             enumerator = this.cache[key] as EnumeratorWrapper;
     113:             if ((enumerator as EnumeratorWrapper).consumed == true)
     114:             {
     115:                 return ((enumerator as EnumeratorWrapper).FromCache());
     116:             }
     117:         }
     118:         else
     119:         {
     120:             //miss
     121:             enumerator = new EnumeratorWrapper(this, this.queryable.GetEnumerator());
     122:             this.cache.Add(key, enumerator, DateTimeOffset.Now.AddSeconds(this.durationSeconds));
     123:         }
     124:  
     125:         return (enumerator);
     126:     }
     127:  
     128:     #endregion
     129:  
     130:     #region IEnumerable Members
     131:  
     132:     IEnumerator IEnumerable.GetEnumerator()
     133:     {
     134:         return (this.GetEnumerator());
     135:     }
     136:  
     137:     #endregion
     138:  
     139:     #region IQueryable Members
     140:  
     141:     public Type ElementType
     142:     {
     143:         get
     144:         {
     145:             return (this.queryable.ElementType);
     146:         }
     147:     }
     148:  
     149:     public Expression Expression
     150:     {
     151:         get
     152:         {
     153:             return (this.queryable.Expression);
     154:         }
     155:     }
     156:  
     157:     public IQueryProvider Provider
     158:     {
     159:         get
     160:         {
     161:             return (this.queryable.Provider);
     162:         }
     163:     }
     164:  
     165:     #endregion
     166:  
     167:     #region Private methods
     168:     private Int32 GetKey(IQueryable queryable)
     169:     {
     170:         return (comparer.GetHashCode(queryable.Expression));
     171:     }
     172:     #endregion
     173: }

    Putting It All Together

    This allows me to write code as this:

       1: //cache miss
       2: var q1 = ctx.Customers.Where(x => x.Orders.Any()).OrderBy(x => x.Name).AsCacheable(TimeSpan.FromSeconds(10)).ToList();
       3:  
       4: //cache hit
       5: var q2 = ctx.Customers.Where(o => o.Orders.Any()).OrderBy(x => x.Name).AsCacheable(TimeSpan.FromSeconds(10)).ToList();
       6:  
       7: //cache hit
       8: var q3 = (from c in ctx.Customers where c.Orders.Any() orderby c.Name select c).AsCacheable(TimeSpan.FromSeconds(10)).ToList();
       9:  
      10: Thread.Sleep(10000);
      11:  
      12: //cache miss
      13: var q4 = ctx.Customers.Where(x => x.Orders.Any()).OrderBy(x => x.Name).AsCacheable(TimeSpan.FromSeconds(10)).ToList();

    By calling the AsCacheable extension method, our LINQ queries get cached for the specified duration. This will work with any LINQ implementation.

    Read more...

  • Dynamic LINQ Extension Method

    Remember those old posts on Dynamic LINQ? You are probably aware that Microsoft has made its implementation available as a Nuget package, but, like I said, you already have it in your machine, hidden inside the System.Web.Extensions assembly.

    In order to make it easier to use, I wrote a simple extension method that works with plain old IQueryable<T>. And here it is:

       1: public static IQueryable<T> Where<T>(this IQueryable<T> query, String restriction, params Object[] values)
       2: {
       3:     Assembly asm = typeof(UpdatePanel).Assembly;
       4:     Type dynamicExpressionType = asm.GetType("System.Web.Query.Dynamic.DynamicExpression");
       5:     MethodInfo parseLambdaMethod = dynamicExpressionType.GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => (m.Name == "ParseLambda") && (m.GetParameters().Length == 2)).Single().MakeGenericMethod(typeof(T), typeof(Boolean));
       6:     Expression<Func<T, Boolean>> expression = parseLambdaMethod.Invoke(null, new Object[] { restriction, values }) as Expression<Func<T, Boolean>>;
       7:  
       8:     return (query.Where(expression));
       9: }

    It even supports parameters! Just two simple examples – I am using Entity Framework, but you can use whatever you like, this is totally generic:

       1: //without parameters
       2: var productsWithPriceGreaterThan100 = ctx.Products.Where("Price > 100").ToList();
       3:  
       4: //with parameters
       5: var productsWithPriceLowerThan100 = ctx.Products.Where("Price < @0", 100).ToList();

    To make it clear, all parameters must be indicated as @0, @1, and so on. It is better to use them, because the database engine can reuse the execution plan.

    There’s one limitation, though: you can’t compare each value on its own, you have to specify one of its properties. For example, you can’t have:

       1: var productNames = ctx.Products.Select(x => x.Name).Where("@it != @0", "Some Name").ToList();

    The @it parameter is not recognized, which is a pity.

    Make sure you have references to both System.Web and System.Web.Extensions, this is required, and won’t affect anything.

    As always, glad to be of service! Winking smile

    Read more...

  • Comparing LINQ Expressions

    Introduction

    I recently came upon this problem: how to calculate a hash from a LINQ expression so that I can safely compare two expressions for equality? I had naively assumed that the Expression class – and their descendants – would have implemented GetHashCode in an appropriate way, so as to make developer’s lifes easier, but unfortunately Microsoft thought otherwise.

    After looking it up on the Internet, I could see two “solutions”:

    • Convert the Expression to its string representation and get the hash code of it;
    • Use an ExpressionVisitor to visit all contained expressions and calculate their individual hash – if this seems recursive to you, that’s because it is!

    Comparing the String Representation

    The first “solution” doesn’t actually work, because two expressions might represent exactly the same and yet have different string representations. For example, consider:

       1: Expression<Func<Int32, Boolean>> isEven = x => (x % 2) == 0;

    and

       1: Expression<Func<Int32, Boolean>> isEven = n => (n % 2) == 0;

    The only thing that differentiates these two expressions is the name of the lambda parameter, unfortunately it causes their string representations to be different.

    One possible solution might be to use a regular expression to get all occurrences of lambda expressions, capture the name of the lambda variables, and then do a search and replace for some well known name:

       1: var original = expression.ToString();
       2: var replaced = original;
       3: var reParameterDeclaration = new Regex(@"(?<it>\w+)\s=>\s");
       4: var matches = reParameterDeclaration.Matches(original);
       5:  
       6: for (var i = 0; i < matches.Count; ++i)
       7: {
       8:     var match = matches[i];
       9:     var it = match.Groups[1].Value;
      10:  
      11:     replaced = Regex.Replace(replaced, String.Format(@"\b{0}\b", it), "it");
      12: }
      13:  
      14: Int32 hashCode = replaced.GetHashCode();

    At first sight – at least, for me! – this seemed to work, however, the replacement pattern – “get me all words composed of only the lambda variable” -  might match something that it wasn’t supposed to, for instance:

       1: //here it works
       2: Expression<Func<Int32, Boolean>> isEven = x => ((x % 2) == 0);
       3:  
       4: //here it doesn't, because the x inside the string is also matched
       5: Expression<Func<String, String>> x => "x y z";

    I might use a different replacement regular expression, for example, I could check for “all lambda variables followed by a dot (.)”:

       1: replaced = Regex.Replace(replaced, String.Format(@"\b{0}\.", it), "it");

    But this wouldn’t get code like this:

       1: //won't work because there's no dot after the lambda variable x
       2: var sortedNames = names.OrderBy(x => x);

    To call it off, I think it might be possible, but it is more complicated than it seems.

    Using an ExpressionVisitor

    The next approach involves using an ExpressionVisitor to visit all expressions contained in an Expression, something that I have talked before. The thing here is, an Expression is typically composed of several other expressions, of various kinds, so appropriate code must be used for each in order to get an univocal hash out of it. For example, for a ConstantExpression, we might consider its NodeType, Type and Value properties. The problem is, there are more than 20 Expression subclasses, and we need to do this for each.

    To cut things short, suppose we have implemented an hash code extractor for each Expression kind. We can’t simply have a big hash code by adding all hash codes calculated for each Expression fragment, because the order by which they appear is also important:

       1: //first take 10 elements and then sort them
       2: var pagedSortedNames = names.Take(10).OrderBy(x => x);
       3:  
       4: //first sort all elements then take 10 of them
       5: var sortedPagedNames = names.OrderBy(x => x).Take(10);

    So, each individual hash code must be take in order, and then we must get the hash code of the entire list of hash codes. Uff!

    Existing Implementations

    This problem exists since there are LINQ expressions, and, of course, other folks have come up with solutions. The NHibernate project has one, probably LINQ to SQL and Entity Framework have too, just to name those more close to me. However, I don’t think any of these solutions might be ready for external, general purpose usage outside their source projects. It would be great to have one such library that we could pick up and use, but I haven’t so far found any. Might be something to work on, though.

    What are your thoughts?

    Read more...

  • Intercepting LINQ Queries

    A common request when working with LINQ queries (Entity Framework, NHibernate, etc) is the ability to intercept them, that is, inspect an existing query and possibly modify something in it. This is not extremely difficult to do “by hand”, but Microsoft has a nice class called ExpressionVisitor which makes the job easier. It basically has virtual methods that get called whenever the class visits each expression contained in a greater expression, which may come from a query (the IQueryable interface exposes the underlying Expression in its Expression property). The virtual methods even allow returning a replacement for each expression found, the only problem is that you must subclass ExpressionVisitor to make even the slightest change, so I wrote my own class, which exposes all node traversal as events, one event for each kind of expression, where you can return an alternative expression, thus changing the original query. Here is the code for it:

       1: public sealed class ExpressionInterceptor : ExpressionVisitor
       2: {
       3:     #region Public events
       4:     public event Func<BinaryExpression, BinaryExpression> Binary;
       5:     public event Func<BlockExpression, BlockExpression> Block;
       6:     public event Func<CatchBlock, CatchBlock> CatchBlock;
       7:     public event Func<ConditionalExpression, ConditionalExpression> Conditional;
       8:     public event Func<ConstantExpression, ConstantExpression> Constant;
       9:     public event Func<DebugInfoExpression, DebugInfoExpression> DebugInfo;
      10:     public event Func<DefaultExpression, DefaultExpression> Default;
      11:     public event Func<DynamicExpression, DynamicExpression> Dynamic;
      12:     public event Func<ElementInit, ElementInit> ElementInit;
      13:     public event Func<Expression, Expression> Expression;
      14:     public event Func<Expression, Expression> Extension;
      15:     public event Func<GotoExpression, GotoExpression> Goto;
      16:     public event Func<IndexExpression, IndexExpression> Index;
      17:     public event Func<InvocationExpression, InvocationExpression> Invocation;
      18:     public event Func<LabelExpression, LabelExpression> Label;
      19:     public event Func<LabelTarget, LabelTarget> LabelTarget;
      20:     public event Func<LambdaExpression, LambdaExpression> Lambda;
      21:     public event Func<ListInitExpression, ListInitExpression> ListInit;
      22:     public event Func<LoopExpression, LoopExpression> Loop;
      23:     public event Func<MemberExpression, MemberExpression> Member;
      24:     public event Func<MemberAssignment, MemberAssignment> MemberAssignment;
      25:     public event Func<MethodCallExpression, MethodCallExpression> MethodCall;
      26:     public event Func<MemberInitExpression, MemberInitExpression> MemberInit;
      27:     public event Func<NewExpression, NewExpression> New;
      28:     public event Func<NewArrayExpression, NewArrayExpression> NewArray;
      29:     public event Func<ParameterExpression, ParameterExpression> Parameter;
      30:     public event Func<RuntimeVariablesExpression, RuntimeVariablesExpression> RuntimeVariables;
      31:     public event Func<SwitchExpression, SwitchExpression> Switch;
      32:     public event Func<TryExpression, TryExpression> Try;
      33:     public event Func<TypeBinaryExpression, TypeBinaryExpression> TypeBinary;
      34:     public event Func<UnaryExpression, UnaryExpression> Unary;
      35:     #endregion
      36:  
      37:     #region Public methods
      38:     public IQueryable<T> Visit<T>(IQueryable<T> query)
      39:     {
      40:         return (this.Visit(query as IQueryable) as IQueryable<T>);
      41:     }
      42:  
      43:     public IQueryable<T> Visit<T, TExpression>(IQueryable<T> query, Func<TExpression, TExpression> action) where TExpression : Expression
      44:     {
      45:         EventInfo evt = this.GetType().GetEvents(BindingFlags.Public | BindingFlags.Instance).Where(x => x.EventHandlerType == typeof(Func<TExpression, TExpression>)).First();
      46:         evt.AddEventHandler(this, action);
      47:  
      48:         query = this.Visit(query);
      49:  
      50:         evt.RemoveEventHandler(this, action);
      51:  
      52:         return (query);
      53:     }
      54:  
      55:     public IQueryable Visit(IQueryable query)
      56:     {