如何使用公共映射方法将LINQ查询中的多个实体映射到新实体

时间:2015-02-12 15:24:31

标签: c# linq dto

以下代码与我在解决方案中的代码非常相似,不同之处仅在于“entitiesA”和“entitiesB”集合的值使用LINQ数据上下文存储在SQL Server数据库中。代码在控制台应用程序中工作得很好,但是当从数据库中检索值时,系统会将“无法将表达式'...'转换为SQL,并且不能将其视为本地表达式。”我做错了什么?

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);
            };
        }
    }
}

3 个答案:

答案 0 :(得分:1)

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


第一步是创建一个包含两个实体的类。在这种情况下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();

答案 1 :(得分:0)

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

您需要从Linq To SQL获取数据(可能通过查询包含VIEW的{​​{1}},这很容易),并且在您收到它并且它在内存中之后(在JOIN)中,然后将其映射到List数据类型。

ALTERNATIVELY 您可以通过将整个表读入内存来下载实体A和实体B列表,如下所示:

EntityC

然后你可以继续使用你已经使用过的表达式,因为你现在正在使用内存列表,因此定期使用LINQ to Objects而不是LINQ to SQL。

答案 2 :(得分:0)

感谢大家花一点时间撰写可能的解决方案和/或评论。所有想法都很棒。实际上原始代码就像假设一样。问题是我使用一个简单的参数调用映射函数,而不是使用一个terciary运算符表达式。类似的东西:

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

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