将实体映射到没有重复代码的DTO

本文关键字:代码 DTO 实体 映射 | 更新日期: 2023-09-27 18:25:10

我正试图了解在N层应用程序中使用实体框架(6)的问题。由于存储库中的数据(包含与数据库的所有通信)应该在更高层(UI、服务等)中使用,因此我需要将其映射到DTO。

在数据库中,有相当多的多对多关系在进行,因此数据结构可能/将在应用程序生命周期的某个地方变得复杂。我偶然发现,在编写存储库方法时,我正在重复完全相同的代码。这方面的一个例子是我的FirmRepository,它包含GetAll()方法和GetById(int firmId)方法。

GetById(int firmId)方法中,我有以下代码(不完整,因为还有很多关系需要映射到DTO):

public DTO.Firm GetById(int id)
    {
        // Return result
        var result = new DTO.Firm();
        try
        {
            // Database connection
            using (var ctx = new MyEntities())
            {
                // Get the firm from the database 
                var firm = (from f in ctx.Firms
                            where f.ID == id
                            select f).FirstOrDefault();
                // If a firm was found, start mapping to DTO object
                if (firm != null)
                {
                    result.Address = firm.Address;
                    result.Address2 = firm.Address2;
                    result.VAT = firm.VAT;
                    result.Email = firm.Email;
                    // Map Zipcode and City
                    result.City = new DTO.City()
                    {
                        CityName = firm.City.City1,
                        ZipCode = firm.City.ZipCode
                    };
                    // Map ISO code and country
                    result.Country = new DTO.Country()
                    {
                        CountryName = firm.Country.Country1,
                        ISO = firm.Country.ISO
                    };
                    // Check if this firm has any exclusive parameters
                    if (firm.ExclusiveParameterType_Product_Firm.Any())
                    {
                        var exclusiveParamsList = new List<DTO.ExclusiveParameterType>();
                        // Map Exclusive parameter types
                        foreach (var param in firm.ExclusiveParameterType_Product_Firm)
                        {
                            // Check if the exclusive parameter type isn't null before proceeding
                            if (param.ExclusiveParameterType != null)
                            {
                                // Create a new exclusive parameter type DTO
                                var exclusiveParameter = new DTO.ExclusiveParameterType()
                                {
                                    ID = param.ExclusiveParameterType.ID,
                                    Description = param.ExclusiveParameterType.Description,
                                    Name = param.ExclusiveParameterType.Name
                                };
                                // Add the new DTO to the list
                                exclusiveParamsList.Add(exclusiveParameter);
                            }
                        }
                        // A lot more objects to map....
                        // Set the list on the result object
                        result.ExclusiveParameterTypes = exclusiveParamsList;
                    }
                }
            }
            // Return DTO
            return result;
        }
        catch (Exception e)
        {
            // Log exception
            Logging.Instance.Error(e);
            // Simply return null
            return null;
        }
    }

这只是一种方法。GetAll()方法将具有完全相同的映射逻辑,从而导致重复代码。此外,当添加更多方法时,即FindSearch方法,需要再次复制相同的映射。当然,这并不理想。

我读过很多关于著名的AutoMapper框架的文章,该框架可以将实体映射到DTO或从DTO映射实体,但由于我有这些多对多关系,它很快就会被AutoMapper配置代码弄得臃肿不堪。我也读过这篇文章,在我看来很有道理:http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/

有没有其他方法可以在不反复复制/粘贴相同代码的情况下做到这一点?

提前感谢!

将实体映射到没有重复代码的DTO

你可以在实体公司(DB.Ffirm)上做一个扩展方法,比如

public static class Extensions
    {
        public static DTO.Firm ToDto(this DB.Firm firm)
        {
           var result = new DTO.Firm();
           result.Address = firm.Address;
           result.Address2 = firm.Address2;
           //...
           return result;    
        }
    }

然后,您可以在代码中的任何位置转换DB.Ffirm对象,如firm.ToDto();

另一种策略是使用类构造函数和explicit和/或implicit转换运算符的组合。它允许您将一个用户定义的实体强制转换为另一个实体。该功能还有一个额外的好处,那就是将流程抽象出来,这样你就不会重复自己了。

DTO.Firm类中,定义显式或隐式运算符(注意:我对类的名称进行假设):

public class Firm {
  public Firm(DB.Firm firm) {
    Address = firm.Address;
    Email = firm.Email;
    City = new DTO.City() {
      CityName = firm.City.City1;
      ZipCode = firm.City.ZipCode;
    };
    // etc.
  }
  public string Address { get; set;}
  public string Email { get; set; }
  public DTO.City City { get; set; }
  // etc.
  public static explicit operator Firm(DB.Firm f) {
    return new Firm(f);
  }
}

然后,您可以在您的存储库代码中使用它,如下所示:

public DTO.Firm GetById(int id) {
  using (var ctx = new MyEntities()) {
    var firm = (from f in ctx.Firms
                where f.ID == id
                select f).FirstOrDefault();
    return (DTO.Firm)firm;
  }
}
public List<DTO.Firm> GetAll() {
  using (var ctx = new MyEntities()) {
    return ctx.Firms.Cast<DTO.Firm>().ToList();
  }
}

这是MSDN中的参考资料。

关于映射:实际上,使用Automapper或在某种方法中完全手动准备映射并不重要(扩展一或其他答案中提到的显式强制转换运算符),关键是将其放在一个位置以便于重用。

请记住,您使用了FirstOrDefault方法,所以实际上为Firm实体调用了数据库。现在,当您使用这个实体的属性,特别是集合时,它们将被延迟加载。如果你有很多电话(正如你在问题中所建议的那样),你可能会面临大量的额外电话,这可能是一个问题,尤其是在foreach循环中。为了检索一个dto,您可能会遇到十几个调用和严重的性能问题。只要重新思考一下,如果你真的需要得到一个如此大的物体及其所有的关系。

对我来说,您的问题要深刻得多,并考虑了应用程序体系结构。我必须说,我个人不喜欢实体框架的存储库模式,以及工作单元模式。它似乎很受欢迎(至少你们可以看看谷歌的查询结果),但对我来说,它不太适合EF。当然,这只是我的观点,你可能不同意我的观点。对我来说,这只是在已经实现的工作单元(DbContext)和存储库(DbSet对象)上构建另一个抽象。考虑到这个话题,我觉得这篇文章很有意思。命令/查询分离的做事方式对我来说似乎更优雅,而且它更适合SOLID规则。

正如我所说,这只是我的观点,你可能同意,也可能不同意。但我希望这能给你一些启发。