【问题标题】:Lambda with Multiple Joins returning strongly typed objects具有多个连接的 Lambda 返回强类型对象
【发布时间】:2012-07-05 16:46:20
【问题描述】:

这是我正在使用的一组简化的表,它们被描述为类。我正在使用 T4 模板来创建简单的 POCO。 我已经删除了所有类的所有非必要属性,这些属性可能会使问题变得混乱

public class MarketingPlan
{
  public guid MarketingPlanID { get; set; }
  public bool Disabled { get; set; }
  public virtual ICollection<MarketingPlanItem> MarketingPlanItems { get; set; }
}

public partial class MarketingPlanItem
{
    public System.Guid MarketingPlanItemID { get; set; }
    public System.Guid MarketingPlanID { get; set; }
    public System.Guid MarketingPlanItemTypeID { get; set; }
    public bool Disabled { get; set; }
    public Nullable<System.Guid> EmailTemplateID { get; set; }
    public virtual EmailTemplate EmailTemplate { get; set; }
    public virtual MarketingPlanItemType MarketingPlanItemType { get; set; }
}

public partial class EmailTemplate
{
    public System.Guid EmailTemplateID { get; set; }
}

public partial class MarketingPlanItemType
{
    public System.Guid MarketingPlanItemTypeID { get; set; }
}

我尝试创建的强类型结果不需要连接到实体框架上下文。这是我对解决方案的尝试。

public MarketingPlan GetMarketingPlanWithItems(Guid marketingPlanID)
{
  var query = 
      this.Context
          .MarketingPlanItems
          .GroupJoin(this.Context.MarketingPlanItemTypes,
                     mpi => mpi.MarketingPlanItemTypeID,
                     mpit => mpit.MarketingPlanItemTypeID,
                     (mpi, mpit) =>
                     {
                       mpi.MarketingPlanItemType = mpit.FirstOrDefault();
                       return mpi;
                     })
          .GroupJoin(this.Context.EmailTemplates,
                     mpi => mpi.EmailTemplateID,
                     et => et.EmailTemplateID,
                     (mpi, et) =>
                     {
                       mpi.EmailTemplate = et.FirstOrDefault();
                       return mpi;
                     })
          .Where(mpi => mpi.Disabled == false);

  var result = 
      this.Context
          .MarketingPlans
          .GroupJoin(query,
                     mp => mp.MarketingPlanID,
                     mpi => mpi.MarketingPlanID,
                     (mp, mpi) =>
                     {
                       mp.MarketingPlanItems = mpi.ToList();
                       return mp;
                     })
          .Where(mp => mp.MarketingPlanID == marketingPlanID)
          .FirstOrDefault();

  return result;
}

现在我意识到我不能在 GroupJoin 中使用真正的匿名函数,因为它会引发以下错误:

带有语句体的 lambda 表达式不能转换为表达式树

我在这个例子中是这样编码的,因为如果我 new 是一个强类型对象,我相信我必须填充我不想做的每个字段。

最终的结果是有;一个MarketingPlan,其中MarketingPlanItems 仅填充不是DisableEmailTemplate,由EmailTemplate 或Null 填充,每个MarketingPlanItem 填充它的MarketingPlanItemType。用于此的 sql 可能看起来像(大致):

SELECT
  mp.*,
  mpi.*,
  mpit.*,
  et.*
FROM
  MarketingPlan mp
  LEFT JOIN MarketingPlanItem mpi 
    on mp.MarketingPlanID = mpi.MarketingPlanID 
  INNER JOIN MarketingPlanItemType mpit 
    on mpi.MarketingPlanItemTypeID = mpit.MarketingPlanItemTypeID
  LEFT JOIN EmailTemplate et
    on mpi.EmailTemplateID = et.EmailTemplateID

有没有办法在 Entity Framework 中使用 Lambda 完成此操作,而无需对数据库执行多个请求?

更新 1

public MarketingPlan GetMarketingPlanWithItems(Guid marketingPlanID)
{
  MarketingPlan result = null;

  var query = this.Context.MarketingPlanItems
                          .Include("MarketingPlan")
                          .Include("MarketingPlanItemType")
                          .Include("EmailTemplate")
                          .Include("EmailTemplate.EmailTemplateCategory")
                          .Where(mp => !mp.Disabled
                                       && !mp.MarketingPlan.Disabled
                                       && mp.MarketingPlanID == marketingPlanID)
                          .ToList();

  var query2 = query.FirstOrDefault();

  if (query2 != null)
  {
    result = query2.MarketingPlan;
    result.MarketingPlanItems = query;
  }

  return result;
}

这最终返回了我需要的东西。

【问题讨论】:

  • 一个真正的匿名函数。它只是不是一个单一的表达式 lambda 表达式。
  • 您真的需要将结果作为新的 MarketingPlan 并在其集合中启用计划吗?我问是因为这最终会创建一个新实体。您不应该返回刚刚启用的计划,并根据需要拥有消费者组吗?

标签: c# .net entity-framework lambda


【解决方案1】:

您不能只退回计划项目吗?

如:

var items = this.Context.MarketingPlanItems
            .Where(x => !x.Disabled && !x.MarketingPlan.Disabled)

返回的每个项目都已经有一个关联的电子邮件模板。只需将其作为item.EmailTemplate 使用,无需手动连接。

为此,您可以最终按item.MarketingPlan.MarketingPlanID 进行分组,例如Dictionary&lt;MarketingPlan, IList&lt;MarketingPlanItem&gt;&gt; 或您自己的自定义Type,代表每个计划及其启用的项目,而实际上不需要创建任何新实体。

为什么不利用 ORM 为您解决关系? 当心 SELECT N+1 问题,并确保 EF 正在获取所有关联实体。 比如使用:

this.Context.MarketingPlanItems
    .Include(i => i.MarketingPlan)
    .Include(i => i.EmailTemplate)

您还可以在 MarketingPlan 类型上编写扩展方法,以返回返回启用项的表达式,并将其与 Linq 一起使用。

【讨论】:

  • 我遇到的问题是我仍然需要MarketingPlanItem 作为结果(因为它具有我排除的其他属性,因为它们对查询不是必需的,例如标题、摘要等)。
  • 正确,我提到的实现实际上返回了整个 MarketingPlanItems,您也可以按 MarketingPlan 对其进行分组,并拥有整个对象。 Include 用于确保 EF 将单个查询中的所有实体作为连接获取,而不是之后通过 ID 延迟加载,用于结果中的每个计划。
【解决方案2】:

如果我没记错的话,您想为 MarketingPlans 加载相关子项。

为什么不使用 Include()?使用这样的东西:

this.Context.MarketingPlans.Include("MarketingPlanItems.EmailTemplates")

【讨论】:

    猜你喜欢
    • 2013-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-21
    • 1970-01-01
    • 1970-01-01
    • 2016-04-12
    相关资源
    最近更新 更多