用Linq到实体标准化和抽象IQueryable构造

时间:2015-03-17 16:28:41

标签: c# linq entity-framework linq-to-entities entity-framework-6

TL; DR;在大型数据访问层中标准化和抽象IQueryable构造的最佳方法是什么?扩展程序是否可以接受或鼓励?

背景

我们正在使用具有存储库模式的Entity Framework 6作为我们的数据访问层。

为了使我们的数据调用更有效率,我们最近开始使用一些结构化数据传输对象来迫使我们自己只从数据库中提取所需的数据。

例如:我们有一个仪表板,它使用实体映射数据库表的500个属性中的15个来创建配置文件的分页摘要。

我们不是在转换中提取完整实体和映射,而是从SELECT语句中直接转换:

//This is a simplified representation
public List<PersonDashboardDTO> GetPeopleByRangeForDashboard(int start, int length)
{
var returnPeople = new List<PersonDashboardDTO>();

IQueryable<PersonForDashboardDTO> People = databaseContext.Profile
    .Where(x => !x.IsDeleted)
    .OrderByDescending(x => x.LastName)
    .Skip(start).Take(length)
    .Select(y => new PersonForDashboardDTO
    {
        Name = String.Concat(y.FirstName, " ", y.LastName),
        Company = y.CompanyContact.Select(x => x.Company.Name).FirstOrDefault(),
        SummaryAddress = y.Address.AddressLine1,
        City = y.Address.City,
        IsEmailOK = y.Notifications.CanSendEmail,
    });

     returnPeople.AddRange(People);
     return returnPeople;
}

虽然这只是一个简单的例子,但其中一些SELECT映射超过了150个属性,并且它反复复制并粘贴一遍又一遍。

似乎还有理由,因为IQueryable在执行到另一个对象(like .ToList(), or List.AddRange(IQueryable<>)之前不执行,我们可以创建方法来抽象更抽象的数据访问调用方式。

我不确定正确的模式是什么,但这就是我的想法:

提案:推广方法

public static IQueryable<PersonDashboardDTO> MapToPersonDashboardDTO(this IQueryable<Profile> profile)
{
    return profile.Select(y => new PersonDashboardDTO
    {
        Name = String.Concat(y.FirstName, " ", y.LastName),
        Company = y.CompanyContact.Select(x => x.Company.Name).FirstOrDefault(),
        SummaryAddress = y.Address.AddressLine1,
        City = y.Address.City,
        IsEmailOK = y.Notifications.CanSendEmail
    });
}

public static IQueryable<Profile> IsNotDeleted(this IQueryable<Profile> profile)
{
    return profile.Where(x => !x.IsDeleted);
}

public static IQueryable<Profile> OrderedByLastName(this IQueryable<Profile> profile)
{
    return profile.OrderByDescending(x => x.LastName);
}

public static IQueryable<Profile> TakeRange(this IQueryable<Profile> profile, int start, int length)
{
    return profile.Skip(start).Take(length);
}

示例实施

public List<PersonDashboardDTO> GetPeopleByRangeForDashboard(int start, int length)
{
    var returnPeople = new List<PersonDashboardDTO>();

    IQueryable<PersonDashboardDTO> People = databaseContext.Profile
        .IsNotDeleted()
        .OrderedByLastName()
        .TakeRange(start, length)
        .MapToPersonDashboardDTO();

    returnPeople.AddRange(People);

    return returnPeople;
}

摘要

这是一个可接受且可用的模式,可以用来标准化我们使用EF6进行的查询吗?这似乎是一个很好的方式,但我不能在这里找到标准和做法的方式,并且会喜欢一些新鲜的眼睛。

1 个答案:

答案 0 :(得分:1)

对我来说似乎很合理,但我会做出以下更改:

public IQueryable<PersonDashboardDTO> GetPeopleByRangeForDashboard(int start, int length)
{
    return databaseContext.Profile
        .IsNotDeleted()
        .OrderedByLastName()
        .TakeRange(start, length)
        .MapToPersonDashboardDTO();
}

在大多数情况下,DAL没有理由将结果转换为List而不是仅返回IQueryable,如果您的应用程序不需要对象的某些字段,它可能/应该在枚举之前重新编写为较小的完整对象它。在大多数情况下,这将导致更快的数据库访问,并且在某些情况下非常显着。特别是如果不使用像Address这样的字段,那么数据库可以删除连接。

public static IQueryable<T> TakeRange<T>(this IQueryable<T> profile, int start, int length)
{
    return profile.Skip(start).Take(length);
}

没有理由只将其附加到返回配置文件的查询中。这适用于任何IQueryable。