In AutoMapper 8.0 missing ResolveUsing - entity-framework

Prior to AutoMapper 8.0, I have used this code:
CreateMap<ApplicationRole, RoleViewModel>()
.ForMember(d => d.Permissions, map => map.MapFrom(s => s.Claims))
.ForMember(d => d.UsersCount, map => map.ResolveUsing(s => s.Users?.Count ?? 0))
.ReverseMap();
The documentation says that you have to change ResolveUsing for MapFrom, but I have a Error "No propagation Null"
.ForMember(d => d.UsersCount, map => map.MapFrom(s => s.Users?.Count ?? 0))
How I have to resolve it?

Replace ResolveUsing with MapFrom, and add one more input parameter to the lambda (TDestination).
.ForMember(d => d.UsersCount, map => map.MapFrom((s,d) => s.Users?.Count ?? 0))
EDIT November 2022
Newest version(s) of AutoMapper doesn't support lambda expression in MapFrom. In this case you have to extract it to a method (Func) or do it inline if you can.
Also, null propagation isn't allowed unless it's a method.
.ForMember(d => d.UsersCount, map => map.MapFrom(s => MapUserCount(s))
--------------
private static int MapUserCount(ApplicationRole src) {
return src.Users?.Count ?? 0;
}
or
.ForMember(d => d.UsersCount, map => map.MapFrom(s => s.Users == null ? 0 : s.Users.Count))

In AutoMapper 8.0 missing ResolveUsing
I also have the same issue and I'm using the following prototype of ResolveUsing:
void ResolveUsing(Func<TSource, TResult> mappingFunction);
Instead of replacing existing code, I preferred to create an extension method:
using System;
using AutoMapper;
namespace myLibrary.Extensions
{
public static class AutoMapperCompatibilityExtensions
{
// Summary:
// Resolve destination member using a custom value resolver callback. Used instead
// of MapFrom when not simply redirecting a source member This method cannot be
// used in conjunction with LINQ query projection
//
// Parameters:
// resolver:
// Callback function to resolve against source type
public static void ResolveUsing<TSource, TDestination, TMember, TResult>(this IMemberConfigurationExpression<TSource, TDestination, TMember> member, Func<TSource, TResult> resolver) => member.MapFrom((Func<TSource, TDestination, TResult>)((src, dest) => resolver(src)));
}
}
Later, in my code, I simply referred the namespace:
using myLibrary.Extensions;
...
... map.ResolveUsing(s => ...
...
Hope this helps.

You don't need to use this expression, you can "Users.Count" and it'll return 0 if the list is empty.

Related

EF Core: Getting the `SUM` of nested collection

I have this code:
decimal returned = await db.ReturnSalesDeliveries
.Include(m => m.Items).ThenInclude(m => m.ReturnSCItem)
.Include(m => m.ReturnSalesContract)
.Where(m => m.ReturnSalesContract.SalesDeliveryId == sdId)
.Select(m => m.Items.Sum(x => x.InQty * x.ReturnSCItem.Price)).DefaultIfEmpty().SumAsync();
It was fine when I write this using EF, but it was not working on EF Core.
Apparently it cannot be translated to SQL.
This is the error:
Cannot perform an aggregate function on an expression containing an
aggregate or a subquery.
How can I rewrite this?
Rewrite query in the following way and note that Includes are not needed if you do not load whole entity.
decimal returned = await db.ReturnSalesDeliveries
.Where(m => m.ReturnSalesContract.SalesDeliveryId == sdId)
.SelectMany(m => m.Items)
.SumAsync(x => x.InQty * x.ReturnSCItem.Price);

GroupBy + OrderByDescending, returning separate id [duplicate]

I need to get top 10 rows for each group in a table with entity framework.
Based on other solution on SO, I tried 2 things:
var sendDocuments = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.Select(t => new
{
t.Key,
Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
})
.ToArrayAsync();
error:
System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId),
ElementSelector:(EntityShaperExpression:
EntityType: DbDocument
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False ) )
.OrderByDescending(t2 => t2.InsertedDateTime)' could not be translated. Either rewrite the query in a form that can be translated,
> or switch to client evaluation explicitly by inserting a call to
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().
and
var sendDocuments2 = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
.ToArrayAsync();
error:
System.InvalidOperationException: 'Processing of the LINQ expression
't => t
.OrderByDescending(t2 => t2.InsertedDateTime)
.AsQueryable()
.Take(10)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Any other idea?
Update (EF Core 6.0):
EF Core 6.0 added support for translating GroupBy result set projection, so the original code for taking (key, items) now works as it should, i.e.
var query = context.Set<DbDocument>()
.Where(e => partnerIds.Contains(e.SenderId))
.GroupBy(e => e.SenderId)
.Select(g => new
{
g.Key,
Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
});
However flattening (via SelectMany) is still unsupported, so you have to use the below workaround if you need such query shape.
Original (EF Core 3.0/3.1/5.0):
This is a common problem, unfortunately not supported by EF Core 3.0/3.1/5.0 query translator specifically for GroupBy.
The workaround is to do the groping manually by correlating 2 subqueries - one for keys and one for corresponding data.
Applying it to your examples would be something like this.
If you need (key, items) pairs:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.Select(key => new
{
Key = key,
Documents =
context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
.ToList() // <--
});
If you need just flat result set containing top N items per key:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
);

How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1

I need to get top 10 rows for each group in a table with entity framework.
Based on other solution on SO, I tried 2 things:
var sendDocuments = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.Select(t => new
{
t.Key,
Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
})
.ToArrayAsync();
error:
System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId),
ElementSelector:(EntityShaperExpression:
EntityType: DbDocument
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False ) )
.OrderByDescending(t2 => t2.InsertedDateTime)' could not be translated. Either rewrite the query in a form that can be translated,
> or switch to client evaluation explicitly by inserting a call to
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().
and
var sendDocuments2 = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
.ToArrayAsync();
error:
System.InvalidOperationException: 'Processing of the LINQ expression
't => t
.OrderByDescending(t2 => t2.InsertedDateTime)
.AsQueryable()
.Take(10)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Any other idea?
Update (EF Core 6.0):
EF Core 6.0 added support for translating GroupBy result set projection, so the original code for taking (key, items) now works as it should, i.e.
var query = context.Set<DbDocument>()
.Where(e => partnerIds.Contains(e.SenderId))
.GroupBy(e => e.SenderId)
.Select(g => new
{
g.Key,
Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
});
However flattening (via SelectMany) is still unsupported, so you have to use the below workaround if you need such query shape.
Original (EF Core 3.0/3.1/5.0):
This is a common problem, unfortunately not supported by EF Core 3.0/3.1/5.0 query translator specifically for GroupBy.
The workaround is to do the groping manually by correlating 2 subqueries - one for keys and one for corresponding data.
Applying it to your examples would be something like this.
If you need (key, items) pairs:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.Select(key => new
{
Key = key,
Documents =
context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
.ToList() // <--
});
If you need just flat result set containing top N items per key:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
);

Loses the connection name when obtaining a record (Outside Laravel / enssegers/laravel-mongodb)

I need your help, I am using Eloquent and enssegers / laravel-mongodb outside Laravel Framework, I have managed to correctly configure both Eloquent and laravel-mongodb and it works correctly to insert and obtain results, however, when trying to modify a registry, eloquent loses the connection name by throwing the following error:
Argument 1 passed to Jenssegers\Mongodb\Query\Builder::__construct() must be an instance of Jenssegers\Mongodb\Connection, instance of Illuminate\Database\MySqlConnection given, called in /www/html/syberianbox/sachiel/vendor/jenssegers/mongodb/src/Jenssegers/Mongodb/Eloquent/Model.php on line 421
I share the configuration of Eloquent through Capsula and laravel-mongodb
$this->_capsule = new Capsule();
$this->_capsule->getDatabaseManager()->extend('mongodb', function($config) {
return new MongodbConnect($config);
});
//MySQL connection, $ this -> _ config contains an array with the correct values
$this->addConnection('default', [
'driver' => $this->_config['default']['driver']
,'host' => $this->_config['default']['host']
,'database' => $this->_config['default']['database']
,'username' => $this->_config['default']['username']
,'password' => $this->_config['default']['password']
,'charset' => $this->_config['default']['charset']
,'collation' => $this->_config['default']['collation']
,'prefix' => $this->_config['default']['prefix']
]);
//MongoDB connection
$this->addConnection('archivos', [
'driver' => $this->_config['archivos']['driver']
,'host' => $this->_config['archivos']['host']
,'port' => $this->_config['archivos']['port']
,'database' => $this->_config['archivos']['database']
]);
Model:
<?php
namespace Instances\Generic\Models\CFDI;
use Jenssegers\Mongodb\Eloquent\Model;
class Archivo extends Model
{
protected $collection = 'cfdi';
protected $connection = 'archivos';
}
Execute model:
$archivoDB = Archvio::Where('_id',$id)->first();
$this->_logger->info('Archivo: ', $archivoDB);
$archivoDB->uuid = $uuid;
$archivoDB->type = $type;
$archivoDB->content = $content;
$archivoDB->save();
Logger:
[2019-02-12 14:20:36:511603][Instances/Generic/Modules/Administracion/ArchivosController.php : 75][Info][30117] Archivo:
Instances\Generic\Models\CFDI\Archivo Object
(
[collection:protected] => cfdi
[connection:protected] =>
[primaryKey:protected] => _id
[keyType:protected] => string
.....
I was able to solve the problem, however it is more a patch than a complete solution.
I extend the class Model of jenssegers / laravel-mongodb and on I write the method getConnectionName () and return the name of the expected connection, as in my case it is only a single connection the name remains as static, however, the method gives the arrow to be able to include all the necessary logic to identify the connection with the model in question
Model:
<?php
namespace Syberianbox\Db;
use Jenssegers\Mongodb\Eloquent\Model;
class Monomodel extends Model
{
public function getConnectionName() {
return 'archivos';
}
}

lamba expression nested .select and using navigation properties in lower level

var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue *
(q.Products.SelectMany(qpm => qpm.ProductMods).Select(qpm => qpm.Quantity)).FirstOrDefault()
);
I would like to do the above like this:
var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue * qpm.Quantity);
but I do not have access to qpm.Quantity value because it's higher level.
any suggestions?
If the relationship between ProductMods and ModDiscounts is one to many you should have a navigation property in ModeDiscount to ProductMod, so your query could be like:
var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue * qmd.ProductMod.Quantity);