ODATA - Query filter push down - asp.net-web-api

I am new to OData, so any help would be appreciated.
I created a test ASP .Net Web API project to query data from SQL Server using OData and Drapper is used instead of EF. I found that the query filter does not get pushed into the query which is executed on the database.
q1. Does the push down work only with EF ?
q2. would OData work for any source which has a ODBC driver.
code snippet
public class InboundMetaDataController : ODataController
{
DapperContext db = new DapperContext();
[EnableQuery]
public IQueryable<InboundMetaData> Get()
{
return GetInboundMeta();
}
public IEnumerable<InboundMetaData> GetInboundMetaRecords()
{
var query = "SELECT Id, DataSource, Client, DataPath FROM Datalake.InboundMetaData ";
using (var connection = db.CreateConnection())
{
return connection.Query<InboundMetaData>(query).AsEnumerable();
}
}
public IQueryable<InboundMetaData> GetInboundMeta()
{
IEnumerable<InboundMetaData> qry = GetInboundMetaRecords();
return qry.AsQueryable();
}
}
public class DapperContext
{
private readonly string _connectionString = #"server=server1; database=test_db; Integrated Security=true; Encrypt=false";
public IQueryable<InboundMetaData> InboundMetaData { get; internal set; }
public IDbConnection CreateConnection()
=> new SqlConnection(_connectionString);
}
Thanks
Manoj George

The .Net OData implementation translates the incoming OData query into a LINQ expression tree. Unfortunately this can't easily be mapped into a Dapper query because it is not translated directly into an SQL query string.
q1. Does the push down work only with EF ?
So the direct answer is NO, this push down doesn't only work with EF, OOTB it will only work with a provider that supports IQueryable expression trees. otherwise you will need to manually parse the expression to SQL.
q2. would OData work for any source which has a ODBC driver.
You can make it work with any backend provider, but depending on the implementation you might have to do a lot of mapping or query building work yourself.
Technically, EF can be made to work with ODBC drivers, the ability to be vendor agnostic is often a reason to use EF in the first place. You can write your own custom implementation when needed, and for many ODBC drivers you might need to.
In regard to Dapper however, Dapper does not support IQueryable. Other users have found specific solution to this: Dapper, ODATA, and IQueryable in ASP.NET by iterating through the expression tree to build the SQL.
The problem with a solution like that, is that you've now manually replicated similar logic to what EF provides for you, and it is likely to take longer to execute than EF that has been specifically optimised for this type of processing. So the only argument that you might have to use Dapper has been invalidated.
In most implementations OData using Dapper will either have significantly reduced query features and possibly will not support aggregates or expansions, but will take longer to execute or will consume significantly more operational memory than if you had used Entity Framework as the ORM.
this is the line that first breaks the concept:
return connection.Query<InboundMetaData>(query).AsEnumerable();
At that point, before the request arguments are evaluated, the raw query without a filter is loaded into memory. .AsEnumerable() is the same as using .ToList() or .ToArray() in terms of moving the data execution into the API and out of the underlying data store.
This is the second anti-pattern in the same method:
using (var connection = db.CreateConnection())
{
return connection. Query<InboundMetaData>(query).AsEnumerable();
}
Because the DB Connection is closed before the controller method has completed execution, even if the output supported an IQueryable expression tree, the EnableQueryAttribute can only operate on the data that is in memory. If the query had not yet been executed, the whole call would fail because the connection has been closed before the query was executed.
For that reason, in OData controllers be tend to declare the DbContext or DbConnection for the lifetime of the request and do not dispose of it before the response content has been serialized.
If you're interested, I've written up a blog article that might help: Should I use Dapper?

Related

OData accent-insensitive filter

How can I apply a filter accent-insensitive? In OData the "eq" operator is case and accent sensitive. The case is easy to fix, because the "tolower" but relative to the accent I'm not getting a simple solution. I know contains is supposed to be accent-insensitive but if I use contains filtering by "São José" I am only getting these responses "São José" and "São José dos Campos", it is missing "Sao Jose".
The following example filtering by "Florianopolis" is expected to return "Florianópolis", but it does not:
url: api/cidades/get?$filter=contains(nome, 'Florianopolis')
[HttpGet]
[EnableQuery]
public ActionResult<IQueryable<CidadeDTO>> Get()
{
try
{
return Ok(_mapper.Map<IEnumerable<CidadeDTO>>(_db.Cidades));
}
catch (System.Exception e)
{
return BadRequest(e.GetBaseException().Message);
}
}
It should bring aswell, like entity framework.
If your OData model was mapped directly to EF models AND an IQueryable<T> expression was passed into OK() then the query is explicitly passed through to the database engine as SQL:
SELECT * FROM Cidades WHERE nome LIKE '%Florianopolis%'
When that occurs, the Collation settings in the database connection will determine the comparison matching logic.
When your database collation is case and accent insensitive, but your data is still filtered as if it was not, then this is an indication that an IEnumerable<T> has been passed into OK() and the comparison logic is being evaluated in C# which by default in insensitive to both case and accent. Unfortunately this means that it is very likely that the entire data table has been loaded into memory first so that the filter can be applied.
In your case the OData model is mapped to DTO expressions that are mapped to the EF models via AutoMapper and that is where the generated query can break down. By calling Map() you are loading ALL records from the EF table and leaving the $filter criteria to be applied by the EnableQueryAttribute
For OData query conventions to be applied automatically you must return an IQueryable<T> from your method, or atleast pass an IQueryable<T> into the OK() response handler. With AutoMapper, you can use the Queryable Extensions to satisfy the IQueryable<T> requirement:
Queryable Extensions
When using an ORM such as NHibernate or Entity Framework with AutoMapper’s standard mapper.Map functions, you may notice that the ORM will query all the fields of all the objects within a graph when AutoMapper is attempting to map the results to a destination type.
...
ProjectTo must be the last call in the chain. ORMs work with entities, not DTOs. So apply any filtering and sorting on entities and, as the last step, project to DTOs.
In OData the last requirement (about ProjectTo) is still problematic because the EnableQueryAttribute will append the query options to the IQueryable<T> response, which will still end up materializing the entire table into memory first (IEnumerable<T>) and then apply the filter, which is still incredibly inefficient. It is this behaviour that is generally observed when someone complains about poor performance from an OData implementation, it is not always AutoMapper, but usually the pattern that the data source is loaded into memory in its entirety and then filtered. Following the default guidance for AutoMapper will lead you in this direction.
Instead we need to use an additional package: AutoMapper.Extensions.ExpressionMapping that will give us access to the UseAsDataSource extension method.
UseAsDataSource
Mapping expressions to one another is a tedious and produces long ugly code.
UseAsDataSource().For<DTO>() makes this translation clean by not having to explicitly map expressions. It also calls ProjectTo<TDO>() for you as well, where applicable.
This changes your implementation to the following:
[HttpGet]
[EnableQuery]
public ActionResult<IQueryable<CidadeDTO>> Get()
{
return Ok(_db.Cidades.UseAsDataSource().For<CidadeDTO>());
}
Don't fall into the trap of assuming that AutoMapper is necessary or best practice for an OData API implementation. If you are not using the unique features that AutoMapper provides then adding an additional abstraction layer can end up over-complicating your solution.
I'm not against AutoMapper, I use it a lot for Integrations, ETL, GraphQL and non-DDD style data schemas where the DTO models are significantly different to the underlying EF/data storage models. But it is a maintenance and performance overhead that a simple DDD data model and OData API based solution can easily do without.
Don't hire an excavator when a simple shovel will do the job.
AutoMapper is a convention based ORM that can be useful when you want to change the structure between implementation layers in your code, traditionally you might map Business Domain models that may represent aggregates or have flattened structures to highly normalised Database models.
OData is also a convention based ORM. It was designed to facilitate many of the same operations that AutoAmpper provides with the exception of Flattening and Unflattening models. These operations are deferred to the EF engine. The types exposed via OData mapping are DTOs
If your DTO models are the same relational structure as your EF models, then you would generally not use AutoMapper at all, the OData Edm mapping is optimised specifically to manage this type of workload and is designed to be and has been integrated directly into the serialization layer, making the Edm truely Data Transfer Objects that only exist over the wire and in the client.
This did the job
[HttpGet]
public ActionResult<IQueryable<PessoaDTO>> Get(ODataQueryOptions<Pessoa> options)
{
try
{
var queryResult = options.ApplyTo(_db.Pessoas);
return Ok(queryResult);
}
catch (System.Exception e)
{
return BadRequest(e.GetBaseException().Message);
}
}

Static methods, non static members - Data Access Layer/Business Layer

OK, i am creating a web application. I am using MVC3. I have ViewModel for every view and also I have Data Model that supports viewModel and does the actuall CRUD operation in my sql table, while my viewModel validates and alters data as necessary.
Here is a question. The way I have been coding is
public class MyClassViewModel
{
public string member1{get;set;}
public int member2{get;set;}
public static GetAllMembers(MyClassViewModel obj, out string strErrMsg)
{
// code goes here, read operation
}
public static UpdateMyClass(MyClassViewModel obj, out string strErrMsg)
{
// code goes here, write operation.
}
}
Both My ViewModel and DataModels have been coded this way, My Controller on HttpPost just do something like this..
MyClassViewModel.UpdateMember(obj,out strErrMsg)
Since its mvc on every post it creates on a strongly typed view, a new object of my ViewModel, hence non static members are different and are not shared between sessions.
I am using Linq and therefore on each static method of my DataModel i use
var db = new MyApplicationDataContext()
to get my linq classes and work on them. This solves my open/close reader problems.
My question is, are there any issues regarding concurrency with this design? I know I might have problems in changing my data layer if it is not designed via interface, but I highly doubt that is necessary based on my application.
You are essentially using the factory design pattern. That's OK pattern to use for this; each static method has its own context, and that's OK too. You always have to worry about concurrency; however, the LINQ to SQL architecture has concurrency detection built in and throws a ChangeConflictException when a concurrent change has been made in reaction to this.
I would also highly recommend that you are disposing your contexts properly at the end of each static method call, because you could leave open connections to the database which can cause performance problems.
Also, another problem you may run into is interlinking data; you cannot link two objects together by reference that were created from different contexts. And in LINQ to SQL, there is no way to explicitly detach an object...

Define business methods usable by linq to entity and linq to objects

I use in my project a lot of LINQ queries and business methods.
To allow these business method to be used from an Iqueryable :
I defined UDF functions in SQL Server (with the needed parameters)
Add this UDF to the EDMX model of the application
And make a gateway between UDF and LinQ with a method like this in a
partial class who inherits from the dbcontext :
[EdmFunction("MyProject.Store", "GetTaxesOfProduct")]
public static Decimal GetTaxesOfProduct(Decimal amount, Int32 TaxMethod)
{
throw new NotSupportedException("Not direct access possible, use with E-SQL or LINQ");
}
This works perfectly for IQueryable.
But the problem is that, to use this method from a simple object (not linked to a database record), i need to make something creepy like this :
var query = from foo in context.JustATable select context.GetTaxesOfProduct(15.55, 3);
And recently i came across this http://blogs.msdn.com/b/charlie/archive/2008/01/31/expression-tree-basics.aspx who explain how, with expression, you can make a method who is usable from C# objects and IQueryable
So, with expression, is it possible to make business methods like my method but without the use of UDF and just expressions ?
Thank you by advance !
It depends on the content of your UDF. Expression can work only with entities defined in your model and use only operations provided by Entity Framework provider for your database. So if you use any complex SQL statement with not supported equivalent for LINQ or non mapped features inside your UDF it will not work.

Workarounds for using custom methods/extension methods in LINQ to Entities

I have defined a GenericRepository class which does the db interaction.
protected GenericRepository rep = new GenericRepository();
And in my BLL classes, I can query the db like:
public List<Album> GetVisibleAlbums(int accessLevel)
{
return rep.Find<Album>(a => a.AccessLevel.BinaryAnd(accessLevel)).ToList();
}
BinaryAnd is an extension method which checks two int values bit by bit. e.g. AccessLevel=5 => AccessLevel.BinaryAnd(5) and AccessLevel.binaryAnd(1) both return true.
However I cannot use this extension method in my LINQ queries. I get a runtime error as follows:
LINQ to Entities does not recognize the method 'Boolean BinaryAnd(System.Object, System.Object)' method, and this method cannot be translated into a store expression.
Also tried changing it to a custom method but no luck. What are the workarounds?
Should I get all the albums and then iterate them through a foreach loop and pick those which match the AccessLevels?
I realize this already has an accepted answer, I just thought I'd post this in case someone wanted to try writing a LINQ expression interceptor.
So... here is what I did to make translatable custom extension methods: Code Sample
I don't believe this to be a finished solution, but it should hopefully provide a good starting point for anyone brave enough to see it through to completion.
You can only use the core extension methods and CLR methods defined for your EF provider when using Entity Framework and queries on IQueryable<T>. This is because the query is translated directly to SQL code and run on the server.
You can stream the entire collection (using .ToEnumerable()) then query this locally, or convert this to a method that is translatable directly to SQL by your provider.
That being said, basic bitwise operations are supported:
The bitwise AND, OR, NOT, and XOR operators are also mapped to canonical functions when the operand is a numeric type.
So, if you rewrite this to not use a method, and just do the bitwise operation on the value directly, it should work as needed. Try something like the following:
public List<Album> GetVisibleAlbums(int accessLevel)
{
return rep.Find<Album>(a => (a.AccessLevel & accessLevel > 0)).ToList();
}
(I'm not sure exactly how your current extension method works - the above would check to see if any of the flags come back true, which seems to match your statement...)
There are ways to change the linq query just before EF translates it to SQL, at that moment you'd have to translate your ''foreign'' method into a construct translatable by EF.
See an previous question of mine How to wrap Entity Framework to intercept the LINQ expression just before execution? and mine EFWrappableFields extension which does just this for wrapped fields.

Does anyone know of anyone working on a LINQ-to-Memcached provider?

As title. I didn't find one via google, at any rate.
Update: thanks for the links from the two answers; this is very useful, but not what I was after - I am curious to see whether it is possible to query an IRepository backed by memcached (or some other distributed cache), backed by a RDBMS. I've really no idea how that might work in practise; I don't know very much about the internals of either distributed caches or LINQ providers.
I'm maybe envisaging something like the cache LINQ provider generating cache-keys based on the query automatically (where query could be Expression> or some kind of Specification pattern implementation), and basically can be plumped down inbetween my app and my DB. Does that sound useful?
If you don't mind throwing NHibernate between them, you can use LINQ to NHibernate to query entities which can be set to use memcached as their cache.
I dont if this is what you want, you can check in this website. In there you can query Memcached as well as query linq to object.
public static IEnumerable<User> GetAllUsers()
{
// Retrieve from cache if it exists, otherwise run the query
return (from u in ctx.Users select u).CachedQuery("allusers");
}
Is this what you want ?
Here is the source code
public static IEnumerable<T> CachedQuery<T>
(this IQueryable<T> query, string key) where T : class
{
if (cache.KeyExists(key))
{
return (IEnumerable<T>)cache.Get(key);
}
else
{
IEnumerable<T> items = query.ToList();
cache.Set(key, items);
return items;
}
}
Because I didn't know what memcached is I googled around and found this link:
http://latebound.blogspot.com/2008/10/using-memcached-from-c.html
Which has a section near the bottom on using LINQ queries over memcached.
I encounter some problem with linq for memcached also. But you should check out the serialization of your linq DBML whether it's Unidirectional or not.
you might have luck for this solution, worth to try out. For me, i sitll having problem with linq, but other object that have [Serilizable] attribute works fine.

Resources