如何使用通用映射方法将多个实体从 LINQ 查询映射到一个新实体

How to map multiple entities from LINQ query into a new entity using a common map method

下面的代码与我的解决方案非常相似,区别仅在于 "entitiesA" 和 "entitiesB" 集合的值存储在 SQL 服务器数据库中,使用LINQ 数据上下文。该代码在控制台应用程序中运行完美,但是当从数据库中检索值时,系统显示 "Could not translate expression '...' into SQL and could not treat it as a local expression."。我做错了什么?

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqSelectDemo
{
    class EntityA
    {
        public int ID { get; set; }
        public string StringValue { get; set; }
    }

    class EntityB
    {
        public int ID { get; set; }
        public int? EntityAID { get; set; }
        public int IntValue { get; set; }
    }

    class EntityC
    {
        public int ID { get; set; }
        public string StringValue { get; set; }
        public int IntValue { get; set; }
    }

    class Program
    {
        static List<EntityA> entitiesA;

        static List<EntityB> entitiesB;

        static EntityC MapEntityC(EntityB entityB, EntityA entityA = null)
        {
            if (entityA == null)
            {
                entityA = entitiesA.FirstOrDefault(entity => (entity.ID == entityB.EntityAID));
            }
            return new EntityC()
            {
                ID = ((entityA != null) ? entityA.ID : 0) + ((entityB != null) ? entityB.ID : 0),
                StringValue = (entityA != null) ? entityA.StringValue : null,
                IntValue = (entityB != null) ? entityB.IntValue : int.MinValue
            };
        }

        static void Main(string[] args)
        {
            entitiesA = new List<EntityA>()
            {
                new EntityA() { ID = 11, StringValue = "string1" },
                new EntityA() { ID = 12, StringValue = "string2" },
                new EntityA() { ID = 13, StringValue = "string3" },
            };
            entitiesB = new List<EntityB>()
            {
                new EntityB() { ID = 21, IntValue = 1, EntityAID = 11 },
                new EntityB() { ID = 22, IntValue = 2 },
                new EntityB() { ID = 23, IntValue = 3, EntityAID = 13 },
            };

            List<EntityC> entitiesC1 = entitiesB.GroupJoin(entitiesA, entityB => entityB.EntityAID, entityA => entityA.ID, (entityB, entityA) => new { entityB, entityA = entityA.SingleOrDefault() })
                                                .Select(entity => MapEntityC(entity.entityB, entity.entityA))
                                                .ToList();

            List<EntityC> entitiesC2 =
            (
                from entityB in entitiesB
                join entityA in entitiesA on entityB.EntityAID equals entityA.ID into tempEntitiesA
                from entityA in tempEntitiesA.DefaultIfEmpty()
                select MapEntityC(entityB, entityA)
            ).ToList();

            foreach (EntityC entityC in entitiesC1)
            {
                Console.WriteLine("ID: {0}, StringValue: '{1}', IntValue: {2}", entityC.ID, entityC.StringValue, entityC.IntValue);
            };

            Console.WriteLine();

            foreach (EntityC entityC in entitiesC2)
            {
                Console.WriteLine("ID: {0}, StringValue: '{1}', IntValue: {2}", entityC.ID, entityC.StringValue, entityC.IntValue);
            };
        }
    }
}

这意味着 Linq To SQL 无法将您对 MapEntity() 的调用映射到相等的 SQL 表达式。

您需要从 Linq To SQL 获取数据(也许通过查询包含您的 JOINVIEW,这很容易做到),并且在您收到数据后它在内存中(在 List 中),然后将其映射到 EntityC 数据类型。

交替 您可以通过将整个表读入内存来下载列表 entitiesA 和 entitiesB,如下所示:

entitiesA = DbContext.EntitiesA.ToList();
entitiesB = DbContext.EntitiesB.ToList();

然后您可以继续使用您使用过的表达式,因为您现在使用的是内存列表,因此使用常规的 LINQ to Objects 而不是 LINQ to SQL。

我认为您不应该创建 linq-to-objectslinq-to-entities 兼容的查询。尽管它们看起来相似,但在功能上它们是完全不同的。所以话虽如此,我提出的解决方案将忽略事物的 linq-to-objects 方面。


第一步是创建一个 class 来容纳我们的两个实体。在这种情况下 EntityAEntityB

public class EntityBandA
{
   public EntityB EntityB { get;set; }
   public EntityA EntityA { get;set; }
}

现在我们需要创建一个IQueryable转换方法。此方法的目的是将我们的新 IQueryable<EntityBandA> 转换为 IQueryable<EntityC>。注意 - 一旦我们对数据库进行迭代,这只会对数据库 运行(即 ToListFirstOrDefault 等)。

static IQueryable<EntityC> MapEntityC(IQueryable<EntityBandA> query)
{
  var query = from e in query
              select new EntityC()
              {
                ID = e.EntityA.ID + e.EntityB,
                StringValue = e.EntityA.StringValue,
                IntValue = e.EntityB != null ? entityB.IntValue : int.MinValue
            };
  return query;
}

请注意,我们删除了一些空检查。使用 linq-to-entities 时不需要它们,因为我们的表达式正在被翻译成 sql。如果没有找到值,它将选择默认值。 (Int:0, String:null)


现在我们需要调整您当前的应用程序以使用我们的新方法

IQueryable<EntityBandA> queryBandA = entitiesB.GroupJoin(entitiesA, entityB => entityB.EntityAID, entityA => entityA.ID, 
                                                 (entityB, entityA) => new EntityBandA 
                                                 { 
                                                    EntityB = entityB, 
                                                    EntityA = entityA.SingleOrDefault()
                                                 });

List<EntityC> entitiesC1 = MapEntityC(queryBandA).ToList();

感谢大家花点时间写一个可能的解决方案 and/or 评论。所有的想法都很棒。实际上,原始代码可以正常工作。问题是我不是使用微不足道的参数而是使用三元运算符表达式来调用映射函数。类似于:

Select(entity => MapEntityC(entity.entityB,
                           (entity.entityA != null) ? entity.entityA : new EntityA()));

LINQ to SQL 无法转换为 SQL 语句。一旦我删除了那个表达式并使用了一个简单的表达式,代码就可以工作了。再次感谢。