엔티티 프레임 워크가 조인을 남겼습니다.


84

모든 u.usergroup을 반환하도록이 쿼리를 어떻게 변경합니까?

from u in usergroups
from p in u.UsergroupPrices
select new UsergroupPricesList
{
UsergroupID = u.UsergroupID,
UsergroupName = u.UsergroupName,
Price = p.Price
};

1
아마도 이것이 도움 될 수 있습니다. 그것은 여기에 또 다른 질문을했다 SO
므 나헴

답변:


135

MSDN에서 적응, EF 4를 사용하여 조인을 떠나는 방법

var query = from u in usergroups
            join p in UsergroupPrices on u.UsergroupID equals p.UsergroupID into gj
            from x in gj.DefaultIfEmpty()
            select new { 
                UsergroupID = u.UsergroupID,
                UsergroupName = u.UsergroupName,
                Price = (x == null ? String.Empty : x.Price) 
            };

2
나는 여기서 gj.DefaultIfEmpty ()를 어디서나 사용할 수 있기 때문에 이것이 더 좋습니다.
Gary

1
'from x in gj.DefaultIfEmpty ()'줄에 대해 설명해 주시겠습니까?
Alex Dresko 2014 년

@AlexDresko이 부분은 join의 모든 결과를 가져오고 오른손 값이없는 경우에는 null을 제공합니다 (객체의 기본값은 null 임). hth
Menahem

2
테이블이 두 개 이상이면 어떻게됩니까?
MohammadHossein R

1
이것은 efcore에서 약간 변경되었습니다. from x in gj.DefaultIfEmpty()됩니다 from p in gj.DefaultIfEmpty(). docs.microsoft.com/en-us/ef/core/querying/…
carlin.scott

30

약간 과잉 일 수도 있지만 확장 메서드를 작성 했으므로 구문을 LeftJoin사용하여 수행 할 수 있습니다 Join(적어도 메서드 호출 표기법에서).

persons.LeftJoin(
    phoneNumbers,
    person => person.Id,
    phoneNumber => phoneNumber.PersonId,
    (person, phoneNumber) => new
        {
            Person = person,
            PhoneNumber = phoneNumber?.Number
        }
);

내 코드는 현재 식 트리에 GroupJoinSelectMany호출을 추가하는 것 이상을 수행하지 않습니다 . 그럼에도 불구하고 표현식을 직접 작성하고 사용자가 지정한 표현식 트리를 수정해야하기 때문에 상당히 복잡해 보입니다.resultSelector LINQ-to-Entities에서 전체 트리를 번역 매개 변수 .

public static class LeftJoinExtension
{
    public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
        this IQueryable<TOuter> outer,
        IQueryable<TInner> inner,
        Expression<Func<TOuter, TKey>> outerKeySelector,
        Expression<Func<TInner, TKey>> innerKeySelector,
        Expression<Func<TOuter, TInner, TResult>> resultSelector)
    {
        MethodInfo groupJoin = typeof (Queryable).GetMethods()
                                                 .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])")
                                                 .MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate<TOuter, TInner>));
        MethodInfo selectMany = typeof (Queryable).GetMethods()
                                                  .Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])")
                                                  .MakeGenericMethod(typeof (LeftJoinIntermediate<TOuter, TInner>), typeof (TInner), typeof (TResult));

        var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>)
                                      ((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners});

        MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector);

        var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>)
                                           (t => t.ManyInners.DefaultIfEmpty());

        ParameterExpression paramUser = resultSelector.Parameters.First();

        ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "t");
        MemberExpression propExpr = Expression.Property(paramNew, "OneOuter");

        LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First());

        MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector);

        return outer.Provider.CreateQuery<TResult>(exprSelectMany);
    }

    private class LeftJoinIntermediate<TOuter, TInner>
    {
        public TOuter OneOuter { get; set; }
        public IEnumerable<TInner> ManyInners { get; set; }
    }

    private class Replacer : ExpressionVisitor
    {
        private readonly ParameterExpression _oldParam;
        private readonly Expression _replacement;

        public Replacer(ParameterExpression oldParam, Expression replacement)
        {
            _oldParam = oldParam;
            _replacement = replacement;
        }

        public override Expression Visit(Expression exp)
        {
            if (exp == _oldParam)
            {
                return _replacement;
            }

            return base.Visit(exp);
        }
    }
}

2
이 확장 fero에 감사드립니다.
Fergers

이것은 여전히 ​​좋습니다. 감사!
TheGeekYouNeed

1
.NET Framework 4.6.2 내에서 테스트했으며 예상대로 작동합니다 (예 : LEFT OUTER JOIN 생성). 그래도 .NET Core에서 작동하는지 궁금합니다. 감사.
Alexei

23

당신의 삶을 더 편하게 만드십시오 (그룹에 참여하지 마십시오) :

var query = from ug in UserGroups
            from ugp in UserGroupPrices.Where(x => x.UserGroupId == ug.Id).DefaultIfEmpty()
            select new 
            { 
                UserGroupID = ug.UserGroupID,
                UserGroupName = ug.UserGroupName,
                Price = ugp != null ? ugp.Price : 0 //this is to handle nulls as even when Price is non-nullable prop it may come as null from SQL (result of Left Outer Join)
            };

2
그룹 가입을 피하는 것은 의견의 문제이지만 확실히 유효한 의견입니다. nullable이 아닌 속성이고 왼쪽 조인이 결과를 제공하지 않으면 Price = ugp.Price실패 할 수 있습니다 Price.

1
위의 내용에 동의하지만 테이블이 두 개 이상인 경우이 접근 방식은 읽고 유지하기가 훨씬 쉽습니다.
Tomasz Skomra 2017

1
ugp == NULL대한 기본값을 확인 하고 설정할 수 있습니다 Price.
Hp93

그냥 완벽 :)
MohammadHossein R

1
대박! 가독성을 위해이 솔루션을 선호합니다. 또한 이것은 더 많은 조인 (즉, 3 개 이상의 테이블에서)을 훨씬 쉽게 만듭니다! 2 개의 왼쪽 조인 (즉, 3 개의 테이블)에 성공적으로 사용했습니다.
Jeremy Morren

4

메서드 호출 표기법을 선호하는 경우 SelectMany결합 된 with를 사용하여 왼쪽 조인을 강제 할 수 있습니다 DefaultIfEmpty. 적어도 Entity Framework 6이 SQL Server를 강타하는 경우. 예를 들면 :

using(var ctx = new MyDatabaseContext())
{
    var data = ctx
    .MyTable1
    .SelectMany(a => ctx.MyTable2
      .Where(b => b.Id2 == a.Id1)
      .DefaultIfEmpty()
      .Select(b => new
      {
        a.Id1,
        a.Col1,
        Col2 = b == null ? (int?) null : b.Col2,
      }));
}

( MyTable2.Col2이 유형의 열입니다 int.) 생성 된 SQL은 다음과 같습니다.

SELECT 
    [Extent1].[Id1] AS [Id1], 
    [Extent1].[Col1] AS [Col1], 
    CASE WHEN ([Extent2].[Col2] IS NULL) THEN CAST(NULL AS int) ELSE  CAST( [Extent2].[Col2] AS int) END AS [Col2]
    FROM  [dbo].[MyTable1] AS [Extent1]
    LEFT OUTER JOIN [dbo].[MyTable2] AS [Extent2] ON [Extent2].[Id2] = [Extent1].[Id1]

나를 위해 이것은 "CROSS APPLY"와 함께 매우 느린 쿼리를 생성합니다.
Meekohi

2

2 개 이상의 왼쪽 조인의 경우 (creatorUser 및 initiatorUser 조인 왼쪽)

IQueryable<CreateRequestModel> queryResult = from r in authContext.Requests
                                             join candidateUser in authContext.AuthUsers
                                             on r.CandidateId equals candidateUser.Id
                                             join creatorUser in authContext.AuthUsers
                                             on r.CreatorId equals creatorUser.Id into gj
                                             from x in gj.DefaultIfEmpty()
                                             join initiatorUser in authContext.AuthUsers
                                             on r.InitiatorId equals initiatorUser.Id into init
                                             from x1 in init.DefaultIfEmpty()

                                             where candidateUser.UserName.Equals(candidateUsername)
                                             select new CreateRequestModel
                                             {
                                                 UserName = candidateUser.UserName,
                                                 CreatorId = (x == null ? String.Empty : x.UserName),
                                                 InitiatorId = (x1 == null ? String.Empty : x1.UserName),
                                                 CandidateId = candidateUser.UserName
                                             };

1

주 모델에서 DefaultIfEmpty ()를 호출하여이 작업을 수행 할 수있었습니다. 이로 인해 지연로드 된 엔티티에 조인을 남길 수 있었으며 더 읽기 쉬운 것 같습니다.

        var complaints = db.Complaints.DefaultIfEmpty()
            .Where(x => x.DateStage1Complete == null || x.DateStage2Complete == null)
            .OrderBy(x => x.DateEntered)
            .Select(x => new
            {
                ComplaintID = x.ComplaintID,
                CustomerName = x.Customer.Name,
                CustomerAddress = x.Customer.Address,
                MemberName = x.Member != null ? x.Member.Name: string.Empty,
                AllocationName = x.Allocation != null ? x.Allocation.Name: string.Empty,
                CategoryName = x.Category != null ? x.Category.Ssl_Name : string.Empty,
                Stage1Start = x.Stage1StartDate,
                Stage1Expiry = x.Stage1_ExpiryDate,
                Stage2Start = x.Stage2StartDate,
                Stage2Expiry = x.Stage2_ExpiryDate
            });

1
여기에서는 전혀 필요하지 않습니다 . 비어 .DefaultIfEmpty()있을 때만 영향을줍니다 db.Complains. db.Complains.Where(...).OrderBy(...).Select(x => new { ..., MemberName = x.Member != null ? x.Member.Name : string.Empty, ... })이 없으면 .DefaultIfEmpty()이미 왼쪽 조인을 수행합니다 ( Member속성이 선택 사항으로 표시되어 있다고 가정 ).

1

UserGroups에 UserGroupPrices 테이블과 일대 다 관계가있는 경우 EF에서 관계가 다음과 같은 코드로 정의되면

//In UserGroups Model
public List<UserGroupPrices> UserGrpPriceList {get;set;}

//In UserGroupPrices model
public UserGroups UserGrps {get;set;}

다음과 같이 간단히 왼쪽 결합 된 결과 집합을 가져올 수 있습니다.

var list = db.UserGroupDbSet.ToList();

왼쪽 테이블에 대한 DbSet이 UserGroupDbSet이라고 가정하면 오른쪽 테이블의 모든 관련 레코드 목록 인 UserGrpPriceList가 포함됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.