AutoMapper是否支持Linq?

Sam*_*hou 22 automapper linq-to-sql

我对使用Lazy加载功能的Linq to SQL非常感兴趣.而在我的项目,我用AutoMapper映射数据库模型域模型(从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);
    }
Run Code Online (Sandbox Code Playgroud)

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
    };
}
Run Code Online (Sandbox Code Playgroud)

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

Ger*_*old 61

虽然@ 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>();
}
Run Code Online (Sandbox Code Playgroud)

它会像手动映射一样.(该CreateMap声明仅用于演示目的.通常,您在应用程序启动时定义映射一次).

因此,只查询映射所需的列,结果IQueryable仍然是原始查询提供程序(linq-to-sql,linq-to-entities,等等).所以它仍然是可组合的,这将转换为WHERESQL中的一个子句:

SelectAll().Where(x => x.Id == Key).SingleOrDefault();
Run Code Online (Sandbox Code Playgroud)

Project().To<T>()v.1.1.0之前的1

  • 请注意,您可能希望将Mapper.CreateMap从此方法移出生产代码.CreateMap只应在应用程序启动时调用一次,因此将所有CreateMaps移动到global.asax中的Application_Start中的某种引导程序/调用是可行的方法. (5认同)
  • 只需使用AutoMapper.QueryableExtensions添加; (3认同)
  • 这个答案应该成为新的答案. (2认同)

Aar*_*ght 25

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

现在考虑以下映射:

Mapper.CreateMap<DB_RoleInfo, DO_RoleInfo>
    .ForMember(dest => dest.Role, opt => opt.MapFrom(src =>
        (Role)Enum.Parse(typeof(Role), src.Name)));
Run Code Online (Sandbox Code Playgroud)

这种映射完全没问题(除非我写错字),但是假设你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);
}
Run Code Online (Sandbox Code Playgroud)

这实际上是有效的,但通过称自己为"可查询",它就是谎言.如果我试图写这个怎么办会发生什么:

public IEnumerable<DO_RoleInfo> SelectSome()
{
    return from ri in SelectAll()
           where (ri.Role == Role.Administrator) ||
                 (ri.Role == Role.Executive)
           select ri;
}
Run Code Online (Sandbox Code Playgroud)

对此非常认真.LINQ到SQL怎么能可能能够成功地把你where变成一个实际的数据库查询?

Linq对DO_RoleInfo课程一无所知.它不知道如何向后映射- 在某些情况下,甚至可能不可能.当然,您可以查看此代码,然后"噢,这很简单,只需在Name列中搜索'管理员'或'执行' ",但您是唯一知道这一点的人. 就Linq to SQL而言,查询纯属无稽之谈.

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

去超市,带回制作Morton Thompson土耳其的食材.

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

  • 这他妈到底是什么?

你可以去市场,你可以通过名字获得特定的成分,但你不能评估我在你那边给你的条件.我不得不"非图"的标准第一.我必须告诉你,这里是我们需要的配方 - 现在去拿它们.


总而言之,这不是Linq to SQL和AutoMapper之间的一些简单的不兼容性.它不是这两个库中任何一个都是唯一的.如何实际映射到非实体类型并不重要- 您可以轻松地手动执行映射,并且您仍然会得到相同的错误,因为您现在正在向Linq提供一组指令那些不再易于理解,处理没有任何特定实体类型的内在映射的神秘类.

此问题是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));
}
Run Code Online (Sandbox Code Playgroud)

这是一个实用程序方法,它为您处理映射并接受原始实体上的过滤器,而不是映射实体.如果您有许多不同类型的过滤器但总是需要执行相同的映射可能会很有用.

就个人而言,我认为通过首先确定需要从数据库中检索的内容,然后进行任何投影/映射,然后最后,如果您需要进一步过滤(您不应该),然后使用ToList()或实现结果,ToArray()并针对本地列表写入更多条件.

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

  • Gert Arnold的答案如下是解决方案.作为参考,这里是相关的API https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions.cs (2认同)