如何在c# LINQ中执行左外连接到对象而不使用join-on-equal -into子句?有办法用where子句来实现吗? 正确的问题: 内连接很简单,我有一个这样的解决方案

List<JoinPair> innerFinal = (from l in lefts from r in rights where l.Key == r.Key
                             select new JoinPair { LeftId = l.Id, RightId = r.Id})

但是对于左外连接,我需要一个解决方案。我的是这样的,但它不工作

List< JoinPair> leftFinal = (from l in lefts from r in rights
                             select new JoinPair { 
                                            LeftId = l.Id, 
                                            RightId = ((l.Key==r.Key) ? r.Id : 0
                                        })

其中JoinPair是一个类:

public class JoinPair { long leftId; long rightId; }

当前回答

如果需要连接和筛选某些东西,可以在连接之外完成。可以在创建集合之后进行筛选。

在这种情况下,如果我在连接条件中这样做,我减少了返回的行。

使用三元条件(= n == null ?"__": n.MonDayNote,)

如果对象为空(因此不匹配),则返回?后面的内容。__,在这种情况下。 否则,返回:,n.MonDayNote后面的内容。

感谢其他贡献者,这是我开始自己的问题。


        var schedLocations = (from f in db.RAMS_REVENUE_LOCATIONS
              join n in db.RAMS_LOCATION_PLANNED_MANNING on f.revenueCenterID equals

                  n.revenueCenterID into lm

              from n in lm.DefaultIfEmpty()

              join r in db.RAMS_LOCATION_SCHED_NOTE on f.revenueCenterID equals r.revenueCenterID
              into locnotes

              from r in locnotes.DefaultIfEmpty()
              where f.LocID == nLocID && f.In_Use == true && f.revenueCenterID > 1000

              orderby f.Areano ascending, f.Locname ascending
              select new
              {
                  Facname = f.Locname,
                  f.Areano,
                  f.revenueCenterID,
                  f.Locabbrev,

                  //  MonNote = n == null ? "__" : n.MonDayNote,
                  MonNote = n == null ? "__" : n.MonDayNote,
                  TueNote = n == null ? "__" : n.TueDayNote,
                  WedNote = n == null ? "__" : n.WedDayNote,
                  ThuNote = n == null ? "__" : n.ThuDayNote,

                  FriNote = n == null ? "__" : n.FriDayNote,
                  SatNote = n == null ? "__" : n.SatDayNote,
                  SunNote = n == null ? "__" : n.SunDayNote,
                  MonEmpNbr = n == null ? 0 : n.MonEmpNbr,
                  TueEmpNbr = n == null ? 0 : n.TueEmpNbr,
                  WedEmpNbr = n == null ? 0 : n.WedEmpNbr,
                  ThuEmpNbr = n == null ? 0 : n.ThuEmpNbr,
                  FriEmpNbr = n == null ? 0 : n.FriEmpNbr,
                  SatEmpNbr = n == null ? 0 : n.SatEmpNbr,
                  SunEmpNbr = n == null ? 0 : n.SunEmpNbr,
                  SchedMondayDate = n == null ? dMon : n.MondaySchedDate,
                  LocNotes = r == null ? "Notes: N/A" : r.LocationNote

              }).ToList();
                Func<int, string> LambdaManning = (x) => { return x == 0 ? "" : "Manning:" + x.ToString(); };
        DataTable dt_ScheduleMaster = PsuedoSchedule.Tables["ScheduleMasterWithNotes"];
        var schedLocations2 = schedLocations.Where(x => x.SchedMondayDate == dMon);

其他回答

这是我使用的LeftJoin实现。注意,resultSelector表达式接受2个参数:来自连接两端的一个实例。在我看到的大多数其他实现中,结果选择器只接受一个参数,这是一个具有左/右或外部/内部属性的“连接模型”。我更喜欢这个实现,因为它具有与内置Join方法相同的方法签名。它也适用于IQueryables和EF。

 var results = DbContext.Categories
      .LeftJoin(
            DbContext.Products, c => c.Id, p => p.CategoryId,
            (c, p) => new { Category = c, ProductName = p == null ? "(No Products)" : p.ProductName })
      .ToList();

public static class QueryableExtensions
{
    public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
           this IQueryable<TOuter> outer, 
           IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, 
           Expression<Func<TInner, TKey>> innerKeySelector, 
           Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        var query = outer
            .GroupJoin(inner, outerKeySelector, innerKeySelector, (o, i) => new { o, i })
            .SelectMany(o => o.i.DefaultIfEmpty(), (x, i) => new { x.o, i });
        return ApplySelector(query, x => x.o, x => x.i, resultSelector);
    }

    private static IQueryable<TResult> ApplySelector<TSource, TOuter, TInner, TResult>(
        IQueryable<TSource> source,
        Expression<Func<TSource, TOuter>> outerProperty,
        Expression<Func<TSource, TInner>> innerProperty,
        Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        var p = Expression.Parameter(typeof(TSource), $"param_{Guid.NewGuid()}".Replace("-", string.Empty));
        Expression body = resultSelector?.Body
            .ReplaceParameter(resultSelector.Parameters[0], outerProperty.Body.ReplaceParameter(outerProperty.Parameters[0], p))
            .ReplaceParameter(resultSelector.Parameters[1], innerProperty.Body.ReplaceParameter(innerProperty.Parameters[0], p));
        var selector = Expression.Lambda<Func<TSource, TResult>>(body, p);
        return source.Select(selector);
    }
}

public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression source, ParameterExpression toReplace, Expression newExpression)
        => new ReplaceParameterExpressionVisitor(toReplace, newExpression).Visit(source);
}

public class ReplaceParameterExpressionVisitor : ExpressionVisitor
{
    public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, Expression replacement)
    {
        this.ToReplace = toReplace;
        this.Replacement = replacement;
    }

    public ParameterExpression ToReplace { get; }
    public Expression Replacement { get; }
    protected override Expression VisitParameter(ParameterExpression node)
        => (node == ToReplace) ? Replacement : base.VisitParameter(node);
}

这是一种SQL语法,与用于内连接和左外连接的LINQ语法相比。 左外连接:

http://www.ozkary.com/2011/07/linq-to-entity-inner-and-left-joins.html

下面的示例在产品和类别之间进行组连接。这本质上是左连接。即使类别表为空,into表达式也会返回数据。要访问类别表的属性,现在必须通过在catList.DefaultIfEmpty()语句中添加from cl从可枚举结果中进行选择。

这是一般形式(已经在其他答案中提供了)

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty()
    select new { Alpha = a, Beta = b_value };

然而,这里有一个解释,我希望能澄清这实际上是什么意思!

join b in beta on b.field1 equals a.field1 into b_temp

本质上创建了一个单独的结果集b_temp,其中有效地包含了右边条目的空“rows”('b'中的条目)。

然后是下一行:

from b_value in b_temp.DefaultIfEmpty()

..遍历该结果集,为右侧的'row'设置默认空值,并将右侧的行连接的结果设置为'b_value'的值(即,如果有匹配的记录,则为右侧的值,如果没有则为'null')。

现在,如果右边是一个单独的LINQ查询的结果,它将由匿名类型组成,它只能是'something'或'null'。如果它是一个可枚举的对象(例如,一个List -其中MyObjectB是一个有2个字段的类),那么它可以具体说明它的属性使用什么默认的“null”值:

var c =
    from a in alpha
    join b in beta on b.field1 equals a.field1 into b_temp
    from b_value in b_temp.DefaultIfEmpty( new MyObjectB { Field1 = String.Empty, Field2 = (DateTime?) null })
    select new { Alpha = a, Beta_field1 = b_value.Field1, Beta_field2 = b_value.Field2 };

这确保了'b'本身不是空的(但它的属性可以是空的,使用您指定的默认空值),这允许您检查b_value的属性,而不会得到b_value的空引用异常。注意,对于可空的DateTime, (DateTime?)类型,即。'nullable DateTime'必须在'DefaultIfEmpty'的规范中指定为null的'Type'(这也适用于'原生'不为空的类型,例如double, float)。

您可以通过简单地链接上述语法来执行多个左外连接。

根据我对类似问题的回答,在这里:

Linq到SQL左外连接使用Lambda语法和连接2列(复合连接键)

获得代码在这里,或克隆我的github回购,并发挥!

查询:

        var petOwners =
            from person in People
            join pet in Pets
            on new
            {
                person.Id,
                person.Age,
            }
            equals new
            {
                pet.Id,
                Age = pet.Age * 2, // owner is twice age of pet
            }
            into pets
            from pet in pets.DefaultIfEmpty()
            select new PetOwner
            {
                Person = person,
                Pet = pet,
            };

λ:

        var petOwners = People.GroupJoin(
            Pets,
            person => new { person.Id, person.Age },
            pet => new { pet.Id, Age = pet.Age * 2 },
            (person, pet) => new
            {
                Person = person,
                Pets = pet,
            }).SelectMany(
            pet => pet.Pets.DefaultIfEmpty(),
            (people, pet) => new
            {
                people.Person,
                Pet = pet,
            });

通过扩展方法的左外连接的实现如下所示

public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
  this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
  , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
  , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    if (outer == null)
      throw new ArgumentException("outer");

    if (inner == null)
      throw new ArgumentException("inner");

    if (outerKeySelector == null)
      throw new ArgumentException("outerKeySelector");

    if (innerKeySelector == null)
      throw new ArgumentException("innerKeySelector");

    if (resultSelector == null)
      throw new ArgumentException("resultSelector");

    return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
  }

  static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
      IEnumerable<TOuter> outer, IEnumerable<TInner> inner
      , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
      , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    var innerLookup = inner.ToLookup(innerKeySelector, comparer);

    foreach (var outerElment in outer)
    {
      var outerKey = outerKeySelector(outerElment);
      var innerElements = innerLookup[outerKey];

      if (innerElements.Any())
        foreach (var innerElement in innerElements)
          yield return resultSelector(outerElment, innerElement);
      else
        yield return resultSelector(outerElment, default(TInner));
     }
   }

然后,resultselector必须处理空元素。外汇。

   static void Main(string[] args)
   {
     var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
     var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };

     var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
     new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });

     foreach (var item in res)
       Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
   }