【问题标题】:Translating Entity Framework model navigation properties into DTOs将实体框架模型导航属性转换为 DTO
【发布时间】:2013-05-16 11:31:44
【问题描述】:

我目前正在从事一个 n 层网络项目。在研究了数据传输对象及其好处之后,我们决定试一试这种模式。我们的 ASP.NET MVC 网站无法直接访问 EF DbContext,而是使用 DTO 发送和接收实体数据。会有一个服务/映射层在 DTO 和实体模型之间进行转换。

我的问题是,将实体模型导航属性转换为其 DTO 的最佳方法是什么?

以下是项目中实体模型及其 DTO 的示例:

实体模型:

public class Payment
{
    public int ID { get; set; }
    public DateTime? PaidOn { get; set; }
    public decimal Amount { get; set; }
    public string Reference { get; set; }

    //Navigation Properties
    public virtual PaymentMechanism PaymentMechanism { get; set; }
    public virtual ICollection<Order> Orders { get; set; }
}

DTO:

public class PaymentDto
{
    public int ID { get; set; }
    public DateTime? PaidOn { get; set; }
    public decimal Amount { get; set; }
    public string Reference { get; set; }

    //--------Navigation Properties - Object Ids--------
    public int PaymentMechanismId { get; set; }
    public ICollection<int> OrderIds { get; set; }
}

可以看出,除了导航属性之外,它们非常相似。我已将它们更改为保存整数 ID(实体的)而不是实体模型。因此,如果需要获取导航属性实体,可以将它们的 Id 传递给服务/映射层函数,该函数将从数据库中检索实体,将它们映射到 DTO 并返回集合。这是一种可以接受的做事方式吗?

我是这个领域的新手,所以我的一些术语可能并不完全正确,但希望您能理解我的意思。如果您需要我澄清或提供任何其他详细信息,请告诉我。

【问题讨论】:

    标签: c# asp.net-mvc entity-framework-5 dto


    【解决方案1】:

    您可以使用投影加载 DTO:

    var paymentDtos = context.Payments
        .Where(p => p.Amount >= 1000m) // just an example filter
        .Select(p => new PaymentDto
        {
            ID = p.ID,
            PaidOn = p.PaidOn,
            Amount = p.Amount,
            Reference = p.Reference,
            PaymentMechanismId = p.PaymentMechanism.ID,
            OrderIds = p.Orders.Select(o => o.ID)
        })
        .ToList();
    

    您必须将 dto 中的 OrderIds 声明为 IEnumerable&lt;int&gt;,而不是 ICollection&lt;int&gt; 才能进行编译。

    我不确定这个密钥集合是否真的有用。如果您想稍后加载订单,您可以在一个单独的服务方法中完成,仅基于 PaymentID,如下所示:

    public IEnumerable<OrderDto> GetPaymentOrders(int paymentID)
    {
        return context.Payments
            .Where(p => p.ID == paymentID)
            .Select(p => p.Orders.Select(o => new OrderDto
            {
                ID = o.ID,
                //etc. mapping of more Order properties
            }))
            .SingleOrDefault();
    }
    

    【讨论】:

    • 感谢您的回答,感谢您的宝贵时间。我会给你一个赞成票,但我没有足够的代表!这个投影示例与 AutoMapper 之类的映射库有何不同?我确切地知道您对键集合的意思,但这是一种保持对实体关系的引用的方式。我想这并不总是必要的,但它可能对某些 DTO 有用。
    • @ChrisWhite:使用 AutoMapper 将强制您首先从数据库加载 Payment 实体,包括所有 Orders(所有列的顺序),然​​后应用 AutoMapper 它将忽略大部分加载属性,因为您在 dto 中的集合仅包含 int。这可能是很多查询开销。上面的投影完全在数据库中执行,只返回投影中请求的列。
    【解决方案2】:

    我通常在这种情况下使用 Automapper。我会为我的主要实体创建一个 Dto 类,并为我的导航属性实体创建一个 Dto,然后让 Automapper 自动执行映射,而无需手动编写映射代码。

    public class PaymentDto
    {
        public int ID { get; set; }
        public DateTime? PaidOn { get; set; }
        public decimal Amount { get; set; }
        public string Reference { get; set; }
    
        //Navigation Properties
        public virtual PaymentMechanismDto PaymentMechanism { get; set; }
        public virtual ICollection<OrderDto> Orders { get; set; }
    }
    
    public class PaymentMechanismDto
    {
    //properties
    }
    
    public class OrderDto
    {
    //properties
    }
    
    
    public class MappingProfile : Profile
    {
            public MappingProfile()
            {
                Mapper.CreateMap< Payment, PaymentDto >();
                Mapper.CreateMap< PaymentMechanism, PaymentMechanismDto >();
                Mapper.CreateMap< Order, OrderDto >();
            }
    }
    

    【讨论】:

    • 这如何回答这个问题?
    • @AymenDaoudi,它展示了一种如何使用 AutoMapper 的方法——它将自动映射这些属性,而无需手动映射......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    相关资源
    最近更新 更多