简体   繁体   English

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

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

The following code is very similar to what I have in my solution, the difference is only that values for "entitiesA" and "entitiesB" collection are stored in a SQL Server database using LINQ data context. 以下代码与我的解决方案非常相似,不同之处仅在于“ entitiesA”和“ entitiesB”集合的值使用LINQ数据上下文存储在SQL Server数据库中。 The code works perfects in the console application, but when values are retrieved from database, the system says "Could not translate expression '...' into SQL and could not treat it as a local expression.". 该代码在控制台应用程序中运行完美,但是当从数据库中检索值时,系统会显示“无法将表达式'...'转换为SQL,也无法将其视为本地表达式。”。 What I'm doing wrong? 我做错了什么?

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

I don't think you should be creating a linq-to-objects and linq-to-entities compatible query. 我认为您不应该创建linq-to-objectslinq-to-entities兼容查询。 Even though they appear similar, functionally they are completely different. 即使它们看起来相似,但在功能上却完全不同。 So with that said, my proposed solution is going to ignore the linq-to-objects side of things. 因此,我提出的解决方案将忽略事物的“ linq-to-objects方面。


First step is to create a class that holds both of our Entities. 第一步是创建一个包含两个实体的类。 In this case EntityA and EntityB 在这种情况下, EntityAEntityB

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

Now we need to create an IQueryable transformation method. 现在我们需要创建一个IQueryable转换方法。 The purpose of this method is to translate our new IQueryable<EntityBandA> into an IQueryable<EntityC> . 此方法的目的是将新的IQueryable<EntityBandA>转换为IQueryable<EntityC> Note - this will only run against the database once we iterate against it (ie ToList , FirstOrDefault , etc...). 注意-仅在我们对数据库进行迭代(即ToListFirstOrDefault等)之后,此操作FirstOrDefault数据库运行。

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

Notice that we removed some of the null checks. 注意,我们删除了一些空检查。 They are not necessary when using linq-to-entities , because our expression is being translated into sql. 当使用linq-to-entities ,它们不是必需的,因为我们的表达式已被翻译成sql。 If no value is found, it will choose the default. 如果找不到任何值,它将选择默认值。 ( Int:0, String:null ) Int:0, String:null


Now we need to adjust your current application to use our new method 现在我们需要调整您当前的应用程序以使用我们的新方法

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

It means Linq To SQL cannot map your call to MapEntity() to an equal SQL expression. 这意味着Linq To SQL无法MapEntity()调用映射到相等的SQL表达式。

You need to fetch data from Linq To SQL (maybe by querying a VIEW that contains your JOIN , that is easy to do) and, after you have received it and it is in memory (in a List ), then map it to the EntityC data type. 您需要从Linq To SQL中获取数据(也许可以通过查询包含JOINVIEW来完成,这很容易做到),并且在接收到数据并将其存储在内存后(在List ),然后将其映射到EntityC数据类型。

ALTERNATIVELY You can just download the lists entitiesA and entitiesB by reading the whole tables into memory, something like this: 另外,您可以通过将整个表读入内存中来下载列表entitiesA和entityB,如下所示:

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

And then you can continue with the expressions that you have used, since you are now using in-memory lists and thus regular LINQ to Objects instead of LINQ to SQL. 然后,您可以继续使用所使用的表达式,因为现在使用的是内存中列表,因此使用的是常规LINQ to Object,而不是LINQ to SQL。

Thank you to all of you for take a moment to write a possible solution and/or a comment. 谢谢大家花一点时间写一个可能的解决方案和/或评论。 All ideas were fantastic. 所有的想法都很棒。 Actually the original code works as suppose to do it. 实际上,原始代码按预期方式工作。 The problem was that I was calling the mapping function not with trivial parameters but using a terciary operator expression. 问题是我不是用平凡的参数而是使用三次运算符来调用映射函数。 Something like: 就像是:

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

That LINQ to SQL can't translate to SQL statement. LINQ to SQL无法转换为SQL语句。 As soon as I removed that expression and used a simple one, the code works. 一旦删除该表达式并使用了一个简单的表达式,该代码就会起作用。 Once again thank you. 再次感谢您。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM