我试图解决这个问题,我在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()
方法将具有完全相同的映射逻辑,从而导致重复的代码。此外,当添加更多方法时,即Find
或Search
方法时,需要再次复制相同的映射。当然,这并不理想。
我已经阅读了很多关于着名的AutoMapper框架的信息,该框架可以将数据映射到DTO或从DTO映射,但由于我有这些多对多的关系,因此使用AutoMapper配置代码很快就会感到臃肿。我也读过这篇文章,这在我看来是有道理的:http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/
有没有其他方法可以一次又一次地复制/粘贴相同的代码?
提前致谢!
答案 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规则。
正如我所说,这只是我的观点,你可能会也可能不同意我的看法。但我希望它能在这里给你一些看法。