【问题标题】:Code a SQL projection and mapping at the same time?同时编写 SQL 投影和映射代码?
【发布时间】:2015-04-02 17:03:03
【问题描述】:

这可以从我们数据库中的 Address 对象中分离出 DDL 对象:

public class DDL {
    public int?   id   { get; set; }
    public string name { get; set; }
}

List<DDL> mylist = Addresses
    .Select( q => new DDL { id = q.id, name = q.name })
    .ToList();

但是,我们希望将 POCO 到 ViewModel 的映射保留在 MVC 控制器代码之外的单个位置。我们想做这样的事情:

List<DDL> mylist = Addresses
    .Select( q => new DDL(q))  // <-- constructor maps POCO to VM
    .ToList();

但是 SQL 不能使用构造函数。上面的对象初始化器不使用函数来映射字段。当然你可以.AsEnumerable().Select( q =&gt; new DDL(q)),但是这会选择 SQL 中的所有字段(包括数据),将其发送到 C#,然后 C# 分割出我们需要的字段(传输我们不需要的数据效率非常低。)

有什么建议吗?我们碰巧正在使用 Entity Framework 6 来提取数据。

【问题讨论】:

标签: c# entity-framework-6


【解决方案1】:

您只需在某处定义表达式并使用它。例如,在您的 ViewModel 中作为静态只读字段。

public class SomeViewModel
{
    public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
    {
        id = o.id,
        name = o.name
    }

    public int id { get; set; }
    public string name { get; set; }
}

// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);

我亲自为自己制作了一个小的静态 Mapper,它可以保存所有地图并为我使用它们。在我所有的 ViewModel 中,地图都是在静态初始化程序中声明的。结果给了我一些感觉就像 AutoMapper,但不需要 lib 或复杂的映射代码(但也不会为你做任何魔法)。

我可以这样写:

MyCustomMapper.Map<Entity, ViewModel>(entity);

并且它被重载以接受 IEnumerables、IQueryables 和单个 ViewModel。我还添加了只有 1 个接受类型参数的泛型类型(实体)的重载。这是对我的要求。

【讨论】:

  • “我从来没有想过空间是移动的东西!”
  • 不要在 IQueryable 对象上使用Mapper.Map,您需要使用Automapper.QueryableExtensions 命名空间。如果您使用 Mapper.Map,您将获得非常糟糕的 SQL 查询。请参阅 Stop using AutoMapper in your Data Access Code 获取 Automapper 作者的消息,其中他解释了可查询的扩展。
  • @ScottChamberlain 我在哪里说我在使用 AutoMapper?
  • @Dr.Zim 很抱歉,但我必须在谷歌上搜索该报价才能知道它是什么。我不是星际迷航 :)
  • @Pluc “我可以写这样的东西:Mapper.Map&lt;Entity, ViewModel&gt;(entity); 并且它被重载以接受 IEnumerables、IQueryables 和单个 ViewModel。” 它不会重载以接受 IQueryables, IQueryable 派生自 IEnumerable 因此它接受可查询作为可枚举但您没有得到任何优化。如果您使用Mapper.Map&lt;T&gt;,它将始终查询查询中所有包含表的所有列,而不是像使用.Project().To&lt;T&gt;() 时那样只查询它需要的列。
【解决方案2】:

您可以使用匿名类型来限制从数据库中选择的内容,然后使用这些字段来构造您的对象:

List<DDL> mylist = Addresses
    .Select( q => new { id, name })
    .AsEnumerable()
    .Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
    .ToList();

【讨论】:

  • 您最好坚持 OP 的 AsEnumerable 建议,而不是 ToArray - 这将节省您创建数组的费用。
  • 不客气——我应该提到,如果你这样做,当你调用 ToList 时,SQL 调用实际上会被执行——AsEnumerable 调用的效果是导致Select 扩展方法解析为Enumerable.Select 而不是Queryable.Select;它不会强制执行查询。当然,这在我们这里的单个表达式中并不重要,但是如果将其分解为许多表达式,它可能会变得很重要。另外,你的; 放错地方了。
  • 如果 AsEnumerable 不查询数据库,那么无论您使用哪个扩展名(Enumerable 或 Queryable),第二次选择都会引发异常。 EF 无法将带参数的构造函数转换为 SQL 查询。
  • @Pluc 我同意,这就是为什么我最初使用.ToArray() 来提取数据...现在无法测试它,我信任 phoog 那个:)
  • @ibiza 这不是一个坏主意,但它基本上就是第一个示例所做的。
【解决方案3】:

您反对使用 3rd 方库吗? Automapper 的 QueryableExtensions 完全符合您的要求。

List<DDL> mylist = Addresses
    .Project().To<DDL>()
    .ToList();

它甚至具有很好的功能,例如能够对转换后的对象进行过滤,并且该过滤器在服务器端执行。

List<DDL> mylist = Addresses
    .Project().To<DDL>()
    .Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
    .ToList();

【讨论】:

  • @Dr.Zim 是的,如果你真的阅读了他们谈论的文章,​​你应该使用他的Addresses.Project().To&lt;DTO&gt;() 方法而不是Mapper.Map&lt;DTO&gt;(Addresses)。该代码已作为 Automapper.QueryableExtensions 命名空间集成到主 automapper 项目中,而不是单独下载。
  • @Dr.Zim 您是否打算将这些 cmets 发布到 Pluk's answer 而不是我的?如果您将它们张贴在那里会很有意义(例如,您为什么说as well as Scott's?)。
  • 你打字比我快。哈哈。如果您使用 AutoMapper,您的答案肯定是最好的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-08
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多