Automapper through relations in Entity Framework - automapper

As a newbie to automapper (v10.0.0) I'm trying to replace one of my queries. I currently use this to generate my response:
var query = from a in _context.ApprovalStatuses.AsNoTracking()
.Include(x => x.ApprovalOrder).ThenInclude(x => x.Worker)
where a.RequestId == request.Id
orderby a.ApprovalOrder.Position
let w = a.ApprovalOrder.Worker
select new RequestApprovalStatusDTO {
AssignedUtc = a.AssignedUtc,
Comments = a.Comments,
DecisionDateUtc = a.ApprovedDateUtc ?? a.RejectedDateUtc,
Email = w.Email,
Name = w.Name,
Uuid = a.Uuid
};
So I started by creating my mapping in my Profile subclass:
CreateMap<ApprovalStatus, RequestApprovalStatusDTO>()
.ForMember(x => x.DecisionDateUtc, x => x.MapFrom(y => y.ApprovedDateUtc ?? y.RejectedDateUtc))
.ForMember(x => x.Email, x => x.MapFrom(y => y.ApprovalOrder.Worker.Email))
.ForMember(x => x.Name, x => x.MapFrom(y => y.ApprovalOrder.Worker.Name));
And then I rewrote the query like so:
var query = _context.ApprovalStatuses
.Include(x => x.ApprovalOrder)
.ThenInclude(x => x.Worker)
.Where(x => x.RequestId == request.Id)
.OrderBy(x => x.ApprovalOrder.Position);
return Ok(_mapper.Map<RequestApprovalStatusDTO>(query));
At runtime, it's telling me
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
Object -> RequestApprovalStatusDTO
System.Object -> BoltOn.RequestApprovalStatusDTO
lambda_method(Closure , object , RequestApprovalStatusDTO , ResolutionContext )
I understand it's telling me that it doesn't know how to convert from object, but I'm not sure why it's trying to do that since query is an IOrderedQueryable<ApprovalStatus>.

Thanks to Lucian's pointer I was able to solve it like so:
var query = _context.ApprovalStatuses
.Where(x => x.Request.Uuid == uuid)
.OrderBy(x => x.ApprovalOrder.Position);
var approvals = await _mapper.ProjectTo<RequestApprovalStatusDTO>(query).ToArrayAsync();
if (approvals.Length == 0)
return NotFound();
return Ok(approvals);

Related

AutoMapper: Store navigation properties to intermediate result for mapping

In my below mapping I have to check for the existing of the navigation property and based on it I have to map the properties
.ForMember(x => x.IsReceived, opt => opt.MapFrom(x => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault())))
.ForMember(x => x.ReceivedOn, opt => opt.MapFrom(x => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()) ? x.DropOff.Contamination.FirstOrDefault(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).MeasurementDate : null))
.ForMember(x => x.Receiver, opt => opt.MapFrom((x) => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()) ? new LookupItem<string, string>
{
Id = x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).ReceivedBy,
Value = FormattingHelper.GetStaffDisplayName(x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).ReceivedByNavigation, null)
} : new LookupItem<string, string>()))
But you can see I have to repeat this object selection check every time for all the properties
x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault())
Is there any way in auto mapper to store this object temporarily and use it for multiple properties rather selecting it this way everytime?
As suggested by #LucianBargaoanu in comment I used IncludeMembers and it worked perfectly!
This is how I implemented it now
I included the property like this with the original map definition
CreateMap<DropOffBarcodedEquipment, DropOffEquipmentBase>()
.IncludeMembers(x => x.DropOff.Contamination.FirstOrDefault(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()))
And then added mapping from Contamination to DropOffEquipmentBase
CreateMap<Contamination, DropOffEquipmentBase>()
.ForMember(x => x.IsReceived, opt => opt.MapFrom(x => x.MeasurementDate.HasValue))
.ForMember(x => x.ReceivedOn, opt => opt.MapFrom(x => x.MeasurementDate))
.ForMember(x => x.Receiver, opt => opt.MapFrom(x => new LookupItem<string, string>()
{
Id = x.ReceivedBy,
Value = FormattingHelper.GetStaffDisplayName(x.ReceivedByNavigation, null)
}));
And this combination works like a charm!
Thanks to #LucianBargaoanu

EF Core 3 evaluate navigation property null on server

I have a request
DbContext.Invoices
.Where(x => x.Status != InvoiceStatus.Draft && x.PaymentMethod == PaymentMethod.DirectDebit)
.Where(x => x.DirectDebitFile == null).ToList();
DirectDebitFile is a reverse navigation property.
Which was working fine in EF Core 2, not sure about how it was evaluated in the final request.
After upgrade to EF Core 3, this request doesn't work anymore and says
System.InvalidOperationException: The LINQ expression 'DbSet<Invoice>
.Where(i => !(i.IsDeleted))
.Where(i => i.ClubId == __CurrentUser_ClubId_0)
.Cast()
.Where(e => e.FederationId == __CurrentUser_FederationId_1)
.Cast()
.Where(e0 => !(e0.Hidden))
.Cast()
.Where(e1 => (int)e1.Status != 0 && (Nullable<int>)e1.PaymentMethod == (Nullable<int>)DirectDebit)
.LeftJoin(
outer: DbSet<DirectDebitFile>
.Where(d => !(d.IsDeleted)),
inner: e1 => EF.Property<Nullable<long>>(e1, "Id"),
outerKeySelector: d => EF.Property<Nullable<long>>(d, "InvoiceId"),
innerKeySelector: (o, i) => new TransparentIdentifier<Invoice, DirectDebitFile>(
Outer = o,
Inner = i
))
.Where(e1 => e1.Inner == null)' 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(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I can rewrite this query and make it work by moving evaluation to the client-side
DbContext.Invoices
.Include(x=>x.DirectDebitFile)
.Where(x => x.Status != InvoiceStatus.Draft && x.PaymentMethod == PaymentMethod.DirectDebit)
.AsEnumerable()
.Where(x => x.DirectDebitFile == null).ToList();
But in this case, of course, the query will pull up all the rows and filtering x.DirectDebitFile == null will be on the client-side. I want this query to be evaluated on the server, please help to achieve that.
Currently, I changed the request as the way as normal SQL check it, by checking one of the joined table field
AppDbContext.Invoices.Include(x => x.DirectDebitFile)
.Where(x => x.Status != InvoiceStatus.Draft)
.Where(x => x.DirectDebitFile.FileName == null);

How to create multilingual menu link programmatically in Drupal 7

I'm trying to create the menu link programmatically. But its not working where source language is other than english. Here is my code.
$language_list = language_list();
foreach ($language_list as $language_code => $language_object) {
$menu_item = array(
'link_title' => t('Fruit'),
'menu_name' => 'menu-main-footer',
'customized' => 1,
'link_path' => $custom_path,
'language' => $language_code,
'weight' => 30,
);
menu_link_save($menu_item);
}
Any one have some idea on this?
I changed my code. And it work for me.
// Create menu translation set.
$menu_translation_set = i18n_translation_set_create('menu_link');
// Create translated menu link for all site enable language.
$language_list = language_list();
foreach ($language_list as $language_code => $language_object) {
// Add Fruit link in menu-main-footer.
// 'change-fruit' is node title.
$fruit_path = drupal_get_normal_path('change-fruit', $language_code);
if (!menu_link_get_preferred($fruit_path, 'menu-main-footer')) {
$menu_item = array(
'link_title' => t('fruit'),
'menu_name' => 'menu-main-footer',
'customized' => 1,
'link_path' => $fruit_path,
'language' => $language_code,
'weight' => 30,
'i18n_tsid' => $menu_translation_set->tsid,
);
menu_link_save($menu_item);
$menu_translation_set->add_item($menu_item, $language_code);
$menu_translation_set->save();
}
}
May be helpful to other.
I had to migrate an old menu to a new one with its localized translations so here is what I did :
$old_name = 'menu-old';
$new_name = 'menu-new';
$old_menu = menu_load($old_name);
if(isset($old_menu)){
$old_mlids = db_query("SELECT mlid from {menu_links} WHERE menu_name=:menu_name", array(':menu_name' => $old_name))->fetchAll();
if(!empty($old_mlids)){
// Clean existing items in new menu.
$new_mlids = db_query("SELECT mlid from {menu_links} WHERE menu_name=:menu_name", array(':menu_name' => $new_name))->fetchAll();
if(!empty($new_mlids)){
foreach($new_mlids as $record){
menu_link_delete($record->mlid);
}
}
// Copy old to new menu.
foreach($old_mlids as $record){
$old_menu_item = menu_link_load($record->mlid);
$new_menu_item_config = array(
'link_title' => $old_menu_item['link_title'],
'link_path' => $old_menu_item['link_path'],
'menu_name' => $new_name,
'customized' => 1,
'weight' => $old_menu_item['weight'],
'expanded' => $old_menu_item['expanded'],
'options' => $old_menu_item['options'],
);
$new_menu_item = $new_menu_item_config;
menu_link_save($new_menu_item);
// Migrate translations.
$languages = language_list('enabled')[1];
foreach($languages as $lang_code => $language_object){
if ($lang_code == language_default('language')) {
continue;
}
$translation_value = i18n_string_translate('menu:item:'.$old_menu_item['mlid'].':title', $old_menu_item['link_title'], array('langcode' => $lang_code));
if($translation_value != $old_menu_item['link_title']){
i18n_string_translation_update('menu:item:'.$new_menu_item['mlid'].':title', $translation_value, $lang_code, $old_menu_item['link_title']);
}
}
}
}
// Delete old menu.
menu_delete(array('menu_name' => $old_name));
}

Yii2 Pagination Issue on Union

I am trying to use Pagination after union of two queries the pagination does not seems to work. However if I try to make one queries without union it works.
The below are the queries.Please help.
//First Query
$first_second = $this->find()->select($strcolumn.', p.featured_name')->from(MYDIRECTORY::tableName().' j')->join('INNER JOIN' ,MYDIRECTORYFEATUREDCLASS::tableName().' p', 'p.featurer_id = j.listdetails_featured_frid')->where(['listdetails_list_frid' => $id['list_id']])->andWhere(['<=', 'listdetails_featured_frid', 2])->andWhere(['listdetails_list_flag' => MYDIRECTORYCLASS::STATUS_ACTIVE])->orderBy(['listdetails_featured_frid'=>SORT_ASC,'listdetails_list_pos'=>new Expression('rand()')]);
//Second Query
$second_list = $this->find()->select($strcolumn.', p.featured_name')->from(MYDIRECTORYCLASS::tableName().' j')->join('INNER JOIN' ,MYDIRECTORYFEATUREDCLASS::tableName().' p', 'p.featurer_id = j.listdetails_featured_frid')->where(['listdetails_list_frid' => $id['list_id']])->andWhere(['>', 'listdetails_featured_frid', 2])->andWhere(['listdetails_list_flag' => MYDIRECTORYCLASS::STATUS_ACTIVE])->orderBy(['listdetails_featured_frid'=>SORT_ASC,'listdetails_list_medname'=>SORT_ASC]);
//Joined Union Query
$joinedquerys=$first_second->union($second_list);
$countQuery = clone $joinedquerys;
$pages = new Pagination(['totalCount' => $countQuery->count(), 'pageSize' => \Yii::$app->params['pagination_limit'],'defaultPageSize' => \Yii::$app->params['pagination_limit'],'forcePageParam' => false,'params' => ['page' => \Yii::$app->request->get('page', 1)] ]);
$resultArray = $joinedquerys->offset($pages->offset)->limit($pages->limit)->asArray()->all();
return $this->render('listing', [
"mylisting" => $resultArray,
"pagination" => $pages
]);
While using the pagination as below , the pagination seems to not work?Any help will be greatly appreciated
echo LinkPager::widget([
'pagination' => $pagination,
'options' => ['class' => 'paginate pag2 clearfix'],
'registerLinkTags' => true,
'prevPageLabel' => \YII::$app->params['linker_page_btn_prev'],
'nextPageLabel' => \YII::$app->params['linker_page_btn_next'],
'maxButtonCount' => \YII::$app->params['linker_page_btn_count'],
'activePageCssClass' => 'current',
'nextPageCssClass' => 'next'
]);
you could use dataProvider
<?php
$joinedquerys=$first_second->union($second_list);
$dataProvider = new SqlDataProvider([
'sql' => $joinedquerys,
]);
?>
I had the same problem, but in addition I had to use the ActiveDataProvider. To achieve this I had to do the following:
$joinedQuery = $first_second->union($second_list);
$dirQuery = MYDIRECTORY::find()->from(['directories' => $joinedQuery]);
$provider = new ActiveDataProvider([
'query' => $dirQuery,
]);
Hope this helps someone :)

Performance issue : How to execute Two lambda expression at once?. "Contains" and "Any" operator used

Sample code
var Ids = _db.Projects.Where(Project=>Project.Title!="test23rdoct")
.Select (pro => pro.Id);
Expression<Func<Company, bool>> masterExpression =
Company => Company.Participants.Any(part => ids.Contains(part.Project.Id));
IQueryable<Object> queryEntity = _db.Companies.Where(masterExpression)
The above query executing twice. Storing ids in the server(sometime ids are more than 50k count). It causes performance issues. Could anybody suggest how to combine these two queries and execute at once?
How about:
var queryEntity = _db.Companies.Where(c => c.Partipants.Any(p => p.Project.Title != "test23rdoct"));
EDIT:
With the complex query, you could also split that:
Func<Project, bool> projectFilter = Project => ((Compare(Convert(Project.Title), "a") > 0) AndAlso ((Convert(Project.Title) != "test23rdoct") AndAlso
(Project.Participants.Any(Participant => (Compare(Convert(Participant.ParticipantRole.Name), "Finance") > 0)) AndAlso
(Project.Participants.Any(Participant => (Convert(Participant.Person.FirstName) != "test1")) AndAlso
Project.Participants.Any(Participant => (Compare(Convert(Participant.Company.Name), "test") > 0))))));
And then do:
var queryEntity = _db.Companies.Where(c => c.Partipants.Any(p => projectFilter(p.Project));
Would something like this using Join suit your needs?
Expression<Func<Company, bool>> masterExpression =
Company => Company.Participants.Join (Ids, p => p.Project.ID, id => id, (p, id) => p).Any();
IQueryable<Object> queryEntity = _db.Companies.Where(masterExpression);
I got this solution for avoiding execution of Lambda twice. To achieve this I used these extension methods Invoke() and AsExpandable(). Its available in Linqkit dll.
Expression<Func<Company, bool>> masterExpression = Company => Company.Participants.Any(part => masterLamba.Invoke(part.Project));
queryEntity = _db.Companies.AsExpandable().Where(masterExpression);

Resources