none
Extending LINQ to entities for query interception RRS feed

  • Question

  • I would like to do something like this :

     

    Code Snippet

    internal sealed class MyQueryProvider : IQueryProvider

    {

       private ObjectContext _context;

       internal MyQueryProvider (ObjectContext context)

       {

          this._context = context;

       }

     

       #region IQueryProvider Members

     

       public IQueryable<TElement> CreateQuery<TElement>   

       (System.Linq.Expressions.Expression expression)

       {

          return this._context.Provider.CreateQuery <TElement> (expression);

       }

     

       public IQueryable CreateQuery (System.Linq.Expressions.Expression expression)

       {

          return this._context.Provider.CreateQuery (expression);

       }

     

       public TResult Execute<TResult> (System.Linq.Expressions.Expression expression)

       {

          // HERE : Manipulate the query before executing it

          try

          {

             return this._context.Provider.Execute (expression);

          }

          catch (Exception e)

          {

             // HERE : Log / transforme exceptions.

             throw;

          }

       }

     

       object IQueryProvider.Execute (System.Linq.Expressions.Expression expression)

       {

          // HERE : Transforme la requˆte

          try

          {

             return this._context.Provider.Execute (expression);

          }

          catch (Exception e)

          {

             // HERE : Log / transforme exceptions.

             throw;

          }

       }

       #endregion

    }

     

     

    But to keep all functionnalities of the ObjectContext, ObjectQuery, ... I need to reimplement everything. Is there a way to easily extend / inherit or anything to easily handle queries interception?

     

    In reality, if I could inherit from ObjectContext and just override the provider, I think this would work. I just would need to intherit from "MyObjectContext"...

    Tuesday, August 5, 2008 5:15 PM

All replies

  • You can write your own provider on top of another provider and use that.

     

    For some additional information, see:

    http://forums.microsoft.com/Forums/ShowPost.aspx?PostID=3623124&SiteID=1

    and:

    http://blogs.msdn.com/jkowalski/archive/2008/06/23/sample-entity-framework-provider-for-oracle.aspx

    Sunday, August 24, 2008 8:11 PM
    Moderator
  • What I see from this extender is to handle the store command, and not the entities.

     

    For what we need, we have to know the entities. For exemples:

     

    For exemples : We have need to transform some "MethodCallExpression" and "MemberExpression" to enable this:

     

    Localization

     

    On localized entities, we generate membres with attributes to know it's should reference an other table in the current thread language.

     

    Our programmer write this:

    Code Snippet
    from a in context.Students
    where a.Remark.Contains ("something")
    select a;

     

    And we change the query to this before giving the query to entity framework :

    Code Snippet

    from a in context.Students

    where a.Remarks.Where (r => r.Culture == "fr-ca").FirstOfDefault().Description.Contains ("something")

    select a;

     

     

    We also have a special call for optimized "Equals" with localized data.

     

    Enable Contains on collection

    We enabled the use of contains on collections so our programmer can write this:

     

    Code Snippet

    int[] someIds = //...;

     

    from a in context.Students

    where someIds.Constains (a.Id)

    select a;

     

    Dynamic filters

    We have generic search patterns where we don't know possibles critierias at compile time. It's an xml files in database who declare what the user can search. So our programmer only need to write this:

     

    Code Snippet

    from a in context.Students.ApplyDynamicFilter ()

    select a;

     

    And we, depending on the context, will add some linq conditions at runtime. For exemple we will send this to entity framework.

     

    Code Snippet

    from a in context.Students

    where (a.Name == "SomeName" || a.BirthDay == 18) && a.BirthYear < 2001

    select a;

     

    Also note we can register a query, and in the same .aspx, call it's execution multiples times without the programmer knowing it (it's a our library doing it) and with different parameters. It's why we need the "ApplyDynamicFilter" to know where to put the dynamic filters.

     

    Other extensions

    We also have other extensions

    • automatic "Audit" (but we don't need to intercept at this level)
    • Change Sum and other scalar functions so they don't execute right now the querty to te store.
    • We intercept queries so the exception thrown are the same as if they do real sql queries.
    • We can change query timeout by configuration.
    • We can log some query exceptions to diagnostic purpose.

    In summary

     

    We don't wan't nor can really act on the store level, but we need to act on the Linq entity level...

     

    Right now, we need to have a query, and explicitly call an other method to execute it:

     

    Code Snippet

    var query = from a in ..... select a;

    var results = Dac.ExecuteQueryAsList<Students> (query);

     

     

    The problem:

    • Our new programmers forget oflten to call the "Dac.ExecuteQueryAsList", so we lose interceptions points.
    • For aggregate methods, they forget to call "Our aggregates methods" (which execute the query only when called by the dac) and we again lose interceptions points.

    We have checked how to extend LINQ by writing our own "IQueryProvider". But the problem we have is we won't be able to use the object context used by entity framework, and we are afraid to be imcompatible with future improvements you will do to entity framework.

     

    I hope it clarify our needs.

     

    Thanks for your times.

    Monday, August 25, 2008 12:16 PM
  • I see. I wonder if you could write an objectcontext wrapper. And yes, you'd override IQueryProvider, but what you would do is simply transform the LINQ query as appropriate, than hand down the transformed expression tree down to the objectcontext.

     

    Now, that said, note that if you don't override things at the CQT/Provider level, then your programmers may still be able to get around your processor if they went ahead and used eSQL queries/query builder methods. You'd need to be careful not to give them access to those, so no matter what you do, you're in the position of needing to either wrap or hide the objectcontext.

    Monday, August 25, 2008 3:32 PM
    Moderator
  • You might be able to do something with LINQKit's AsExpandable wrapper:
    http://www.albahari.com/nutshell/linqkit.html

    This contains an expression visitor & expression rewriting logic to address limitations in LINQ to SQL - the same principle might work for LINQ to Entities as well.

    Joe
    Tuesday, August 26, 2008 8:21 AM
  • In the LINQKit, we still need to call the AsExpandable () at every query to get the ability to extend EF.

     

    So it's the same problem to remember to call this method everywhere, else we still lose our interception point.

     

    I think we could inherit from ObjectQuery<T> and implement the System.Linq.IQueryable.Provider to plug our provider. But we have the problem the autogenerated properties to access entitysets looks like this :

     

    Code Snippet

    private global::System.Data.Objects.ObjectQuery<TaskServer> _TaskServers;

    [global::System.ComponentModel.BrowsableAttribute(false)]

    public global::System.Data.Objects.ObjectQuery<TaskServerCommand> TaskServerCommands

    {

     get

     {

       if ((this._TaskServerCommands == null))

       {

          this._TaskServerCommands =

             base.CreateQuery<TaskServerCommand>("[TaskServerCommands]");

       }

       return this._TaskServerCommands;

     }

    }

     

     

    So we can't seem to ben able to use our wrapper with the autogenerated code. We could add to the autogenerated code our new properties which would use our wrapper, but again, our programmers need to know to call the new properties to access the entitysets and not the old one.

     

    It's ok if the eSQL queries/query builder methods is not working with our extensions. We choosed only LINQ to do the dataaccess.

     

    Any other idea?

    Tuesday, August 26, 2008 12:12 PM
  • You shouldn't be deriving in order to wrap. Make your own ObjectContext-like class. Declare the actual object context as internal. You could probably define a base ObjectContextWrapper class and within it implement a lot of the intercept logic. Then create one that is specific to your application which will expose parts of the context the way you want.

    Tuesday, August 26, 2008 2:59 PM
    Moderator
  • The problem if we don't derive from ObjectContext is your ObjectContext and my Wrapper won't have any class / interfaces in common. So if we wan't to use an external library needing the ObjectContext (or if microsoft add fonctionnalities to it), we will be incompatible.

    Tuesday, August 26, 2008 5:03 PM