【问题标题】:Entity Framework Data Transfer Objects Best Practice实体框架数据传输对象最佳实践
【发布时间】:2015-11-19 14:07:46
【问题描述】:

我们必须为我们的许多表使用数据传输对象,因为它们非常大,而且许多列对我正在工作的上下文没有用处。

为了获得最佳性能,我无法读取完整的数据库实体并将其转换为 dtos。因此,我创建了一个 linq 扩展方法,在执行查询之前将其转换为 dtos。

调用扩展方法:

db.MyTable.Select(...).ToDto().ToList();

我的扩展方法:

public static IQueryable<MyTableDTO> ToDto(this IQueryable<MyTable> query)
{
     return query.Select(x => new MyTableDTO
     {
         ID = x.ID,
         Name = x.Name
     });
}

这是一个可行的解决方案还是有更好的做法来做到这一点?

第二个问题:不仅有 IQueryable 对象需要转换为 dto,MyTable 对象也必须转换。我为 MyTable 类创建了一个扩展方法:

public static MyTableDto ToDto (this MyTable x)
{
    return new MyTableDto
    {
        ID = x.ID,
        Name = x.Name
    };
}

为什么我不能在我的第一个 ToDto 函数中使用这个函数? 喜欢:

public static IQueryable<MyTableDTO> ToDto(this IQueryable<MyTable> query)
{
    return query.Select(x => x.ToDto());
}

更新

由于以下研究,还有一个问题。在某些情况下,我们只想返回最少的字段来解决高性能问题。

可以创建一个存储库类,您可以在其中定义一个参数来传递一个带有查询应该返回的字段的 Func(如下所述)。然后可以创建一个类(在下面的示例中为 MyServiceClass),您可以在其中为不同的返回实体调用相同的存储库方法。但这是一个好的做法还是更好的解决方案?

public class MyTableRepository<T>
{
    public List<T> GetMyTable(String search1, String search2, Func<MyTable, T> selectExp)
    {
        using(var db = new Context())
        {
            return db.MyTable.Where(x => x.A == search1 ...).Select(selectExp).ToList();
        }
    }
}

public class MyServiceClass
{
    public List<MyTableEntitySimple> GetMyTableEntitySimple(String  search1...)
    {
        MyTableRepository<MyTableEntitySimple> rep = new ...
        return rep.GetMyTable(search1, ToMyTableEntitySimple);
    }

    public List<MyTableEntity> GetMyTableEntity(String search1...)
    {
        MyTableRepository<MyTableEntity> rep = new ...
        return rep.GetMyTable(search1, ToMyTableEntity);
    }

    Func<MyTable, MyTableEntitySimple) ToMyTableEntitySimple = x => new MyTableEntitySimple
    {
        ID = x.ID,
        Name = x.Name
    };

    Func<MyTable, MyTableEntity) ToMyTableEntity = x => new MyTableEntitySimple
    {
        ID = x.ID,
        Name = x.Name,
        Field3 = x.Field3,
        Field4 = x.Field4,
        ...
    };
}

【问题讨论】:

  • 1.这是可行的,您可以利用许多可用的库来避免需要手动映射属性。看看nuget.org/packages/AutoMappernuget.org/packages/Mapster 2。不太清楚为什么不能,你看到什么错误?
  • 您可以避免所有这些,只需使用 .Select(x => new MyTableDto { ID = x.ID, Name = x.Name }),直到您使用 ToList( ) 您仍然只处理查询并对那些字段进行选择,而不是全部。
  • timothy:谢谢,请参阅下面的答案... Stephen:问题是,我不想在返回实体的每种方法中都这样做。我希望每个实体只进行一次转换。

标签: c# entity-framework linq dto


【解决方案1】:

因为您的 Linq to Entities 提供程序不知道如何将您的方法调用转换为 SQL 语句。作为您的问题的解决方案,您可以使用 lambda 表达式而不是扩展方法:

Func<MyTable, MyTableDTO> selectExp=x => new MyTableDTO{
                                                         ID = x.ID,
                                                         Name = x.Name
                                                        });

//Pass the lambda expression as a paremter
public static IQueryable<MyTableDTO> ToDto(this IQueryable<MyTable> query, Func<MyTable, MyTableDTO> selectExpr)
{
    return query.Select(selectExpr);
}

或者按照@Timothy 在他的评论中的建议,您也可以使用Automapper。将实体类与其 DTO 映射后,您可以执行以下操作:

using AutoMapper.QueryableExtensions;

public static IQueryable<MyTableDTO> ToDto(this IQueryable<MyTable> query)
{
    return query.ProjectTo<MyTableDTO>();
}

您可以在此page 中找到更多信息。

更新

对于我的第一个解决方案,也许你可以创建一个通用的扩展方法:

 public static IQueryable<T> ToDto<TSource,T>(this IQueryable<TSource> query, Func<TSource, T> selectExpr)
 {
    return query.Select(selectExpr);
 }

关于第二个,恕我直言,我仍然认为它更适合您,您可以配置映射:

// Configure AutoMapper
Mapper.CreateMap<MyTable, MyTableDTO>()
    .ForMember(dest => dest.YourNewName1, opt => opt.MapFrom(src => src.YourGermanName1))
    .ForMember(dest => dest.YourNewName2, opt => opt.MapFrom(src => src.YourGermanName2));

您可以在link 中找到有关此主题的精彩文章。

【讨论】:

  • 谢谢,效果很好。 (我必须向 query.Select(selectExpr) 添加一个 AsQueryable()。是否可以为我的所有实体创建一个通用 ToDto?(与 lamda 表达式一样,映射已经定义)
  • 我查看了 Automapper。问题是我们的数据库列大多有德语名称,我不想在 dtos 中有德语字段名称。因此,我仍然必须单独映射每个字段
  • 谢谢,我将进一步了解 Automapper 以及我们如何以最佳方式使用它。我为我的问题添加了更新,因为它仍然符合主题,我需要为我们的问题找到最佳解决方案。
  • 这非常有用。我做到了,但我的团队问我:如果我的实体有几个我不需要公开的 LARGE varchar 字段,那么当调用 ToDTO 时,它是否只获取它真正明确需要的字段?还是在实体转换为其对应的 DTO 之前,实体的每个字段仍然被获取?
【解决方案2】:

我还要补充一点,如果您打算只将这些用作 DTO,那么您应该在从 SQL 源枚举之前使用 .AsNoTracking()

返回一个新查询,其中返回的实体不会缓存在 DbContext 或 ObjectContext 中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 2010-09-14
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多