How to map complex linq to object

Viewed 611

Looking for a some help with Ef Core and Linq. Lets say I'm making big request to receive all support tickets with info about product, company, and so on. It's quite simple, just joining things:

select * 
from Tickets T
left join Products P on T.ProductId = P.Id                  
left join ProductVersions PV on T.ProductVersionId = PV.Id  
left join TicketTypes TT on T.TicketTypeId = TT.Id          
left join TicketPriorities TP on T.TicketPriorityId = TP.Id 
left join TicketStates TS on T.TicketStateId = TS.Id    

left join AbpTenants A on T.TenantId = A.Id                 
    left join AbpEditions E on A.EditionId = E.Id   

left join TicketLinkedUsers TLU on TLU.TicketId = T.Id      
    left join TicketLinkTypes TLT on TLT.Id = TLU.TicketLinkTypeId  

However I have issues with last 4 joins.

In project I'm using Ef Core. That is how I made it (partially):

var query = (from o in filteredTickets
                     join o1 in _productRepository.GetAll() on o.ProductId equals o1.Id into j1
                     from s1 in j1.DefaultIfEmpty()
                     join o2 in _productVersionRepository.GetAll() on o.ProductVersionId equals o2.Id into j2
                     from s2 in j2.DefaultIfEmpty()
                     join o3 in _ticketTypeRepository.GetAll() on o.TicketTypeId equals o3.Id into j3
                     from s3 in j3.DefaultIfEmpty()
                     join o4 in _ticketPriorityRepository.GetAll() on o.TicketPriorityId equals o4.Id into j4
                     from s4 in j4.DefaultIfEmpty()
                     join o5 in _ticketStateRepository.GetAll() on o.TicketStateId equals o5.Id into j5
                     from s5 in j5.DefaultIfEmpty()
                     join o6 in _tenantManager.Tenants on o.TenantId equals o6.Id into j6
                     from s6 in j6.DefaultIfEmpty()
                     // join o7 in _editionaRepository.GetAll() on s6.EditionId equals o7.Id into j7
                     // from s7 in j7.DefaultIfEmpty()
                     // join o8 in _ticketLinkedUsersRepository.GetAll() on o.Id equals o8.TicketId into j8
                     // from s8 in j8.DefaultIfEmpty()
                     // join o9 in _ticketLinkTypesRepository.GetAll() on s9.TicketLinkTypeId equals o9.Id into j9
                     // from s9 in j9.DefaultIfEmpty()
                     select new GetTicketForView() { Ticket = ObjectMapper.Map<TicketDto>(o)
                        , ProductName = s1 == null ? "" : s1.Name.ToString()
                        , ProductVersionName = s2 == null ? "" : s2.Name.ToString()
                        , TicketTypeName = s3 == null ? "" : s3.Name.ToString()
                        , TicketPriorityName = s4 == null ? "" : s4.Name.ToString()
                        , TicketState = ObjectMapper.Map<TicketStateTableDto>(s5)
                        , Tenant = ObjectMapper.Map<TenantShortInfoDto>(s6)
                     })

To receive data I'm using repository pattern. And then all data mapped to ViewModel using AutoMapper. This is how my ViewModel looks like:

public class GetTicketForView
{
    public TicketDto Ticket { get; set; }

    public TenantShortInfoDto Tenant { get; set; }

    public string ProductName { get; set;}

    public string ProductVersionName { get; set;}

    public TicketStateTableDto TicketState { get; set; }

    public string TicketTypeName { get; set;}

    public string TicketPriorityName { get; set;}

    public List<TicketLinkedUserDto> LinkedUsers { get; set; }
}

Now I try to get info about Company (AbpTenants) with info about Edition (one to many relationship) and List of TicketLinkedUsers (many to many relationship) with TicketLinkType info. Schema: enter image description here

I can receive all required data using additional joins, however I don't know how to correctly bind and map data to GetTicketForView. List of LinkedUsers and Tenant with nested mapping of Edition is an issue here. Right now I'm doing separate request for each ticket to make many-to-many work:

// execute to get tickets
tickets = await query
           .OrderBy(input.Sorting ?? "ticket.id asc")
           .PageBy(input)
           .ToListAsync();

// then for each ticket get related users:
foreach (var ticket in tickets)
{
    var linkedUsers = _ticketLinkedUsersRepository
   .GetAllIncluding(lu => lu.TicketLinkType, lu => lu.User)
   .OrderBy(a => a.TicketLinkType.Ordinal)
   .Where(p => p.TicketId == ticket.Ticket.Id).ToList();

   ticket.LinkedUsers = ObjectMapper.Map<List<TicketLinkedUserDto>>(linkedUsers);
}

Which is a time-consuming, cause I can get all data in one request using o8 and 09, but I make additional request for each ticket. Don't have enough experience to make it right.

So the question is, how can I implement the first request using linq and map it to ViewModel. Should I use additional request? Or it will be better to use Ef Core Api to make a complex request? Or it's not possible to map complex things inside linq?

Thanks in advance

1 Answers

Following the tips, I rewrote everything

Query:

var query = filteredTickets // IQueryable<Ticket>
             .Include(ten => ten.Tenant)
                .ThenInclude(ed => ed.Edition)
             .Include(p => p.Product)
             .Include(pv => pv.ProductVersion)
             .Include(tt => tt.TicketType)
             .Include(tp => tp.TicketPriority)
             .Include(ts => ts.TicketState)
             .Include(lu => lu.LinkedUsers)
                .ThenInclude(tlt => tlt.TicketLinkType)
             .Include(lu => lu.LinkedUsers)
                .ThenInclude(u => u.User)
            .ProjectTo<GetTicketForView>();

Mappings:

configuration.CreateMap<Ticket, TicketDto>();
configuration.CreateMap<Tenant, TenantShortInfoDto>();
configuration.CreateMap<TicketState, TicketStateTableDto>();
configuration.CreateMap<TicketLinkedUser, TicketLinkedUserDto>();

configuration.CreateMap<Ticket, GetTicketForView>()
            .ForMember(dest => dest.Ticket, conf => conf.MapFrom(src => src)
);

Not sure if I lost some flexibility here if I would like to switch to the nhibernate in the future, but it works much better than before.

Related