24

我对具有延迟加载功能的 Linq to SQL 非常感兴趣。在我的项目中,我使用 AutoMapper 将 DB 模型映射到域模型(从DB_RoleInfoDO_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
    };
}

这种方法按我想要的方式工作。

4

2 回答 2

67

虽然@Aaronaught 的回答在撰写本文时是正确的,但世界经常发生变化,AutoMapper 也随之改变。同时,QueryableExtensions添加到代码库中,增加了对转换为表达式的投影的支持,最后是 SQL。

核心扩展方法是ProjectTo1。这就是您的代码的样子:

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 等)的列。所以它仍然是可组合的,这将转化为WHERESQL 中的一个子句:

SelectAll().Where(x => x.Id == Key).SingleOrDefault();

1 Project().To<T>()在 v. 4.1.0 之前

于 2012-09-11T08:43:41.233 回答
26

将您的第二个功能更改为:

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 一起工作得很好,但它不能作为延迟查询的一部分执行。ToList()在 Linq 查询末尾添加会导致它立即评估结果,而不是尝试将 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。它不知道如何向后映射——在某些情况下,这甚至是不可能的。当然,您可能会查看这段代码并说“哦,这很简单,只需在列中搜索 'Administrator' 或 'Executive' Name,但您是唯一知道这一点的人。 就 Linq to SQL 而言,查询纯属无稽之谈。

想象一下有人给了你这些指示:

去超市把制作莫顿汤普森火鸡的原料带回来。

除非你以前做过,而且大多数人没有做过,否则你对该指令的反应很可能是:

  • 这他妈到底是什么?

你可以去市场,你可以通过名称获得特定的成分,但你不能在你在那里的时候评估我给你的条件。我必须首先“取消映射”标准。我必须告诉你,这是我们制作这个食谱所需的原料——现在去拿吧。


总而言之,这并不是Linq to SQL 和 AutoMapper 之间的一些简单的不兼容。这不是这两个库中的任何一个所独有的。您实际上如何映射到非实体类型并不重要- 您可以轻松地手动进行映射,但您仍然会遇到相同的错误,因为您现在向 Linq to SQL 提供了一组指令不再可理解的,处理没有到任何特定实体类型的内在映射的神秘类。

这个问题是O/R 映射和延迟查询执行概念的基础。投影是一种 单向操作。一旦您进行了项目,您就不能再回到查询引擎并说哦,顺便说一下,这里还有一些条件供您使用。太晚了。你能做的最好的就是接受它已经给你的东西,然后自己评估额外的条件。


最后但并非最不重要的一点是,我会给你一个解决方法。如果您希望能够从映射中做的唯一事情是过滤行,您可以这样写:

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()使用or实现结果ToArray()并针对本地列表编写更多条件。

不要尝试使用 AutoMapper 或任何其他工具来隐藏 Linq 向 SQL 公开的真实实体。域模型是您的公共接口。您编写的查询是您私有实现的一个方面。了解差异并保持良好的关注点分离非常重要。

于 2010-02-06T05:18:16.340 回答