将实体映射到DTO而无需重复的代码

时间:2014-09-30 17:20:56

标签: c# entity-framework entity-framework-6 dto

我试图解决这个问题,我在N层应用程序中使用Entity Framework(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/

有没有其他方法可以一次又一次地复制/粘贴相同的代码?

提前致谢!

3 个答案:

答案 0 :(得分:2)

你可以像这样在实体公司(DB.Firm)上制作一个扩展方法,

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.Firm对象,例如firm.ToDto();

答案 1 :(得分:0)

另一种策略是使用类构造函数和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中的reference

答案 2 :(得分:0)

关于映射:如果您使用Automapper或者在某些方法中完全手动准备映​​射(实际上是扩展一个或作为其他答案中提到的显式强制转换操作符),实际上并不重要 - 重点是将它放在一个地方用于可重用性。

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

对我来说,您的问题更深入,并考虑应用程序架构。我必须说,我个人不喜欢带有Entity Framework的存储库模式,另外还有Unit Of Work模式。它似乎非常受欢迎(至少你看看谷歌搜索结果),但对我来说它不适合EF。当然,这只是我的意见,你可能不同意我的观点。对我来说,它只是在已经实现的工作单元(DbContext)和存储库(DbSet对象)上构建另一个抽象。考虑到这个话题,我发现this article非常有意义。命令/查询分离方式对我来说似乎更加优雅,而且它更适合SOLID规则。

正如我所说,这只是我的观点,你可能会也可能不同意我的看法。但我希望它能在这里给你一些看法。

相关问题