我对使用Lazy加载功能的Linq to SQL非常感兴趣。在我的项目中,我使用AutoMapper将数据库模型映射到域模型(从DB_RoleInfo
到DO_RoleInfo
)。在我的存储库代码中,如下所示:
public DO_RoleInfo SelectByKey(Guid Key)
{
return SelectAll().Where(x => x.Id == Key).SingleOrDefault();
}
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
SelectAll
方法运行良好,但是当我调用SelectByKey
时,我收到错误:
方法“RealMVC.Data.DO_RoleInfo MapDB_RoleInfo,DO_RoleInfo”无法转换为SQL。
Automapper是否完全不支持Linq?
我尝试了下面的手动映射代码,而不是Automapper:
public IQueryable<DO_RoleInfo> SelectAll()
{
return from role in _ctx.DB_RoleInfo
select new DO_RoleInfo
{
Id = role.id,
name = role.name,
code = role.code
};
}
这种方法按照我想要的方式工作。
答案 0 :(得分:59)
虽然@ Aaronaught的答案在撰写本文时是正确的,但世界经常发生变化,AutoMapper也随之而来。与此同时,QueryableExtensions
被添加到代码库中,增加了对转换为表达式的投影的支持,最后是SQL。
核心扩展方法是ProjectTo
1 。这就是您的代码的样子:
using AutoMapper.QueryableExtensions;
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return _ctx.DB_RoleInfo.ProjectTo<DO_RoleInfo>();
}
它会像手动映射一样。 (CreateMap
语句用于演示目的。通常,您在应用程序启动时定义一次映射。
因此,只查询映射所需的列,结果是IQueryable
仍然具有原始查询提供程序(linq-to-sql,linq-to-entities,等等)。因此它仍然是可组合的,这将转换为SQL中的WHERE
子句:
SelectAll().Where(x => x.Id == Key).SingleOrDefault();
1 Project().To<T>()
答案 1 :(得分:24)
将您的第二个功能更改为:
public IEnumerable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo.ToList()
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
AutoMapper适用于Linq to SQL,但它不能作为延迟查询的一部分执行。在Linq查询末尾添加ToList()
会导致它立即评估结果,而不是尝试将AutoMapper段转换为查询的一部分。
一旦您将结果类型更改为非数据实体的内容,延迟执行(不“延迟加载”)的概念就没有任何意义。考虑这两个类:
public class DB_RoleInfo
{
public int ID { get; set; }
public string Name { get; set; }
}
public class DO_RoleInfo
{
public Role Role { get; set; } // Enumeration type
}
现在考虑以下映射:
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>
.ForMember(dest => dest.Role, opt => opt.MapFrom(src =>
(Role)Enum.Parse(typeof(Role), src.Name)));
这种映射完全没问题(除非我写错字),但是假设你在原帖中写了SelectAll
方法而不是我修改过的方法:
public IQueryable<DO_RoleInfo> SelectAll()
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return from role in _ctx.DB_RoleInfo
select Mapper.Map<DB_RoleInfo, DO_RoleInfo>(role);
}
这实际上是有效的,但通过称自己为“可查询”,它就是谎言。如果我试图反对它,会发生什么:
public IEnumerable<DO_RoleInfo> SelectSome()
{
return from ri in SelectAll()
where (ri.Role == Role.Administrator) ||
(ri.Role == Role.Executive)
select ri;
}
对此非常认真。 Linq to SQL 可能如何能够成功地将where
转换为实际的数据库查询?
Linq对DO_RoleInfo
课程一无所知。它不知道如何进行映射向后 - 在某些情况下,甚至可能不可能。当然,您可以查看此代码并转到“哦,这很简单,只需在Name
列中搜索'管理员'或'执行',但你是唯一一个知道这一点的人。就Linq to SQL而言,这个查询纯属无稽之谈。
想象一下有人给了你这些指示:
前往超市并带回制作Morton Thompson Turkey的配料。
除非你以前做过,而大多数人都没有,否则你对该指令的回应很可能是:
你可以去市场,你可以通过名字获得特定的成分,但是当你在那里时,你无法评估我给你的条件。我必须“取消映射”标准第一个。我必须告诉你,这里是我们需要的配方 - 现在去拿它们。
总而言之,这不是Linq to SQL和AutoMapper之间的一些简单的不兼容。它不是这两个库中任何一个都是唯一的。 如何实际上映射到非实体类型并不重要 - 您可以轻松地手动执行映射,并且您仍然会得到相同的错误,因为您现在正在给予Linq to SQL一组不再易于理解的指令,处理没有任何特定实体类型的内在映射的神秘类。
此问题是 O / R Mapping和延迟查询执行概念的基础。 投影是单向操作。一旦你投射,你就不能再回到查询引擎并且顺便说出哦,这里有更多的条件。太晚了。你能做的最好的事情就是把它给你的东西,并自己评估额外的条件。
最后但并非最不重要的是,我会给你一个解决方法。如果您希望能够从映射中执行的仅事件过滤行,则可以写下:
public IEnumerable<DO_RoleInfo> SelectRoles(Func<DB_RoleInfo, bool> selector)
{
Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>();
return _ctx.DB_RoleInfo
.Where(selector)
.Select(dbr => Mapper.Map<DB_RoleInfo, DO_RoleInfo>(dbr));
}
这是一个实用程序方法,它为您处理映射并接受原始实体上的过滤器,不映射实体。如果您有许多不同类型的过滤器但总是需要进行相同的映射,那么它可能会很有用。
就我个人而言,我认为通过首先确定需要从数据库中检索 ,然后进行任何投影/映射,然后最后,如果您需要进行进一步过滤(您不应该),然后使用ToList()
或ToArray()
实现结果并写出更多条件本地清单。
不要尝试使用AutoMapper或任何其他工具来隐藏Linq to SQL公开的真实实体。域模型是您的公共接口。您撰写的查询是私人实施的一个方面。了解差异并保持良好的关注点分离非常重要。