Entity Framework数据传输对象最佳实践

9

我们需要使用数据传输对象来处理许多表格,因为它们非常大,很多列在我工作的上下文中没有用。

为了获得最佳性能,我不能读取完整的数据库实体,然后将其转换为数据传输对象。因此,我创建了一个 LINQ 扩展方法,在执行查询之前将其转换为数据传输对象。

调用扩展方法:

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< MyTable >对象转换为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,
        ...
    };
}

4
  1. 这是可行的,有很多库可供使用,可以避免手动映射属性。请查看 https://www.nuget.org/packages/AutoMapper/ 或 https://www.nuget.org/packages/Mapster/
  2. 我不太确定为什么你不能这样做,你看到了什么错误?
- timothyclifford
1
你可以避免所有这些麻烦,直接使用 .Select(x => new MyTableDto { ID = x.ID, Name = x.Name }),在执行类似于 ToList() 的查询之前,你仍然只是在处理查询,并且只对这些字段进行选择而不是全部字段。 - Stephen Brickner
Timothy:谢谢,下面是答案... Stephen:问题是,我不想在每个返回实体的方法中都这样做。我希望每个实体只进行一次转换。 - Pinzi
2个回答

6

因为您的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>();
}

您可以在此页面中查找更多信息。

更新

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

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

你可以在这个链接找到一篇关于这个主题的优秀文章。

谢谢,它完美地工作了。(我不得不添加一个AsQueryable()到query.Select(selectExpr)。是否可能为我的所有实体创建一个通用的ToDto?(因为使用lambda表达式已经定义了映射) - Pinzi
我看过Automapper。问题是我们的数据库列大多有德语名称,我不想在dtos中使用德语字段名称。因此,我仍然需要单独映射每个字段。 - Pinzi
谢谢,我会进一步研究Automapper以及如何最好地使用它。我在我的问题中添加了更新,因为它仍然符合主题,我需要找到最佳解决方案来解决我们的问题。 - Pinzi
这非常有用。我已经完成了,但我的团队问我:如果我的实体有几个大的varchar字段,我不需要暴露,当调用ToDTO时,它只会显式获取它真正需要的字段吗?还是在将实体转换为相应的DTO之前仍然获取实体的每个字段? - Eakan Gopalakrishnan

0
我也想补充一点,如果你打算仅将它们用作 DTO,则应该在从 SQL 数据源枚举之前使用 .AsNoTracking()

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接