将实体映射到没有重复代码的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()
方法将具有完全相同的映射逻辑,从而导致重复代码。此外,当添加更多方法时,即Find
或Search
方法,需要再次复制相同的映射。当然,这并不理想。
我读过很多关于著名的AutoMapper框架的文章,该框架可以将实体映射到DTO或从DTO映射实体,但由于我有这些多对多关系,它很快就会被AutoMapper配置代码弄得臃肿不堪。我也读过这篇文章,在我看来很有道理:http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/
有没有其他方法可以在不反复复制/粘贴相同代码的情况下做到这一点?
提前感谢!
你可以在实体公司(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规则。
正如我所说,这只是我的观点,你可能同意,也可能不同意。但我希望这能给你一些启发。