如何将复杂的 linq 映射到对象

Posted

技术标签:

【中文标题】如何将复杂的 linq 映射到对象【英文标题】:How to map complex linq to object 【发布时间】:2019-02-06 14:15:21 【问题描述】:

寻求有关 Ef Core 和 Linq 的帮助。 假设我提出了很大的请求,以接收所有包含产品、公司等信息的支持票。这很简单,只需加入一些东西:

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  

但是,我在最后 4 次加入时遇到了问题。

在项目中,我使用的是 Ef Core。我就是这样做的(部分):

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

为了接收数据,我使用了存储库模式。然后使用 AutoMapper 将所有数据映射到 ViewModel。这就是我的 ViewModel 的样子:

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; 

现在我尝试使用有关版本(一对多关系)和 TicketLinkedUsers 列表(多对多关系)的信息以及 TicketLinkType 信息来获取有关公司(AbpTenants)的信息。架构:

我可以使用额外的连接接收所有需要的数据,但是我不知道如何正确地将数据绑定和映射到 GetTicketForView。带有版本嵌套映射的 LinkedUsers 和租户列表是这里的一个问题。现在我正在为每张票单独请求以进行多对多工作:

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

这很耗时,因为我可以使用 o8 和 09 在一个请求中获取所有数据,但我对每张票都提出了额外的请求。没有足够的经验来做对。

所以问题是,如何使用 linq 实现第一个请求并将其映射到 ViewModel。我应该使用额外的请求吗?还是使用 Ef Core Api 发出复杂的请求会更好?或者不可能在 linq 中映射复杂的东西?

提前致谢

【问题讨论】:

查询中的filteredTickets是什么?如果您通过 TicketTypeId 进行枚举,那么您将获得所有票作为单个对象。 也许你应该试试 ProjectTo。 一般来说,你不应该在 EF 中使用 join,你应该使用 Navigation 属性。 @jdweng filteredTickets 它是 _ticketRepository.GetAll() ,根据用户过滤器有几个 WhereIf() 。过滤在这里无关紧要 @NetMage 即使我用 .Include() 和 ThenInclude() 重做查询,主要问题是对对象进行投影/映射 【参考方案1】:

按照提示,我重写了所有内容

查询:

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

映射:

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

如果我将来想切换到 nhibernate,不确定我是否会失去一些灵活性,但它比以前好得多。

【讨论】:

默认情况下 ProjectTo 会获取所有内容,因此不需要 Include-s。 我看到它是 EF Core,所以我不再那么确定了 :) 但值得尝试,因为在 EF6 中它就是这样工作的。 @LucianBargaoanu 它获取,我还发现 LinkedUsers 的 n+1 问题,因为它是 n 对 n 请注意,EF Core 3 取消了多个查询 (N+1),只执行了一个(可能更大的)单个多连接查询。 听起来不错,应该检查一下

以上是关于如何将复杂的 linq 映射到对象的主要内容,如果未能解决你的问题,请参考以下文章

通过 linq 到 xml 的复杂类型映射

LINQ 将数据表映射到列表<MyObject>

如何将具有嵌套对象的复杂 json 文件映射到 java 对象?

如何将复杂的 Python 对象映射到 pandas 数据框?

LINQ体验(18)——LINQ to SQL语句之视图和继承支持

关于 Linq 到 SQL 映射对象设计的建议