Automapper ProjectTo does not allow mapping properties to custom methods - linq

I'm using Entity Framework 6.2.0 and Automapper 6.2.2. I need to map the entity Cart to CartDto. CartDto has a property Total which needs to be mapped to the result of Cart.GetTotal(). I'd like to use .ProjectTo to simplify the query, but if I do that I receive the error:
LINQ to Entities does not recognize the method GetTotal()
because the projection uses IQueryable and the method has no translation in SQL. Is there any way around this issue?
var automapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Cart, CartDto>()
.ForMember(x => x.Total, o => o.MapFrom(x => x.GetTotal()))
.ForAllOtherMembers(x => x.Ignore());
});
var cartDto = dbContext.Carts
.ProjectTo<CartDto>(automapperConiguration)
.FirstOrDefault();

This is not AutoMapper not allowing this, it's Entity Framework. AutoMapper merely takes your mapping configuration and builds a Select LINQ expression to the underlying query provider.
In your case, the underlying query provider is Entity Framework, which unsurprisingly won't understand any random method and how to translate that method into SQL. EF understands a few methods, like Count, Sum, some string/DateTime/primitive methods, but that's about it.
So the exception message is quite accurate - EF doesn't understand this method you've given it, and therefore has no way to translate that method to SQL.
There may be hope, however, if you check out the AutoMapper.EF6 package. It includes the DelegateDecompiler package that uses IL inspection to look at what your method does, de-compile it, and pass that result as an expression to the query provider.
If that seems complicated, it is, so you really better have an understanding of how LINQ works before proceeding.

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);
}
}

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.

Linq to entity - Call user defined method from query

In my project, i use several linq queries for getting prices list.
I need to calculate values based on these prices.
Is it possible to call a user method (who can, ideally, be in the entity class) directly from the linq query, for example, doing like this would be perfect
from foo in Foo
select new {
price = foo.Price,
priceclass = foo.GetClassOfPrice()
}
There would be no data access from GetClassOfPrice, just static code based on the price.
Thank's by advance !
Linq-To-Entities can call only special type of methods defined in conceptual model (EDMX). These methods are called Model defined functions. So if you define your method this way you will be able to call it. You can also check this blog post.
You can only call the method via LINQ to Objects as there is no translation to SQL for the method call. If you materialize the query -- bring it into memory -- first, then do the selection it should work.
var foos = context.Foo.ToList()
.Select( f => new
{
price = f.Price,
priceClass = f.GetClassOfPrice()
} );
Note that you should perform any conditional logic (Where) before doing the ToList so that you're only transferring the data that you actually need from the DB. I'm using extension methods because it's more natural for me and because you'd need to use the ToList or similar method anyway. I really dislike mixing LINQ syntax with the extension methods.
Unfortunately, this can't be done, because your LINQ query is translated to SQL. And your method isn't known to the so called provider that does this translation.
There are only a few functions that you can call when dealing with linq to entities and they are listed here:
http://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.aspx
http://msdn.microsoft.com/en-us/library/system.data.objects.entityfunctions.aspx

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.

EntityFramework Casting issues

I am building my query using PredicateBuilder from LinqKit.
it is great and does exactly what i am looking for.
To make my code more reusable (tables and views) i created a generic predicate builder class:
public class LocalPredicateBuilder<T> where T : IResort
...
var predicate = PredicateBuilder.True<T>(
which exposes BuildPredicate method. I can use it like this:
var predicate = new LocalPredicateBuilder<Resort>().BuildPredicate();
var resorts = _entities.Resorts.Where(predicate).ToList();
however when i try to do this, i get this runtime error (btw entity objects implement IResort):
Unable to cast the type 'ConsoleApplication1.Entities.Resort' to type 'ConsoleApplication1.Entities.IResort'. LINQ to Entities only supports casting Entity Data Model primitive types
i tried casting (didn't work):
var rlist = eq.Cast<Resort>().ToList();
Any other way i can get around this casting issue?
UPDATE
not having much luck getting predicates to work using interfaces.. so i solved my problem by going with POCOs.
Well, the error is accurate. You can't do that in an L2E query, because your interface (IReport) is not part of your entity model and hence can't be converted to SQL. You have to use an entity type, not an interface for that.
just create a partial class for the entity frameowrk object and make that implement the interface.
the other way would be to create a list of the type you want
then do a for each on the linq dataset and add the items to the collection.
the problem is caused because .net doesnt know how to cast
List<ISomething> into a List<Something>

Resources