Khung pháp nhân còn lại tham gia


84

Làm cách nào để thay đổi truy vấn này để nó trả về tất cả u.usergroups?

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

1
có thể điều này có thể giúp ích. nó là một câu hỏi khác ở đây trên SO
Menahem

Câu trả lời:


135

được điều chỉnh từ MSDN, cách kết nối trái bằng 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
Tôi thích điều này hơn là nơi gj.DefaultIfEmpty () ở cuối bởi vì tôi có thể sử dụng x trong where hoặc select!
Gary,

1
Bạn có thể giải thích dòng 'from x in gj.DefaultIfEmpty ()' không?
Alex Dresko,

@AlexDresko phần này lấy tất cả các kết quả từ phép nối và đối với những kết quả không có giá trị bên phải, cho bạn giá trị null (mặc định của đối tượng là null). hth
Menahem

2
Nếu có nhiều hơn hai bảng thì sao?
MohammadHossein R

1
Điều này đã thay đổi một chút với efcore; from x in gj.DefaultIfEmpty()trở thành from p in gj.DefaultIfEmpty(). docs.microsoft.com/en-us/ef/core/querying/…
carlin.scott

30

Nó có thể hơi quá mức cần thiết, nhưng tôi đã viết một phương thức mở rộng, vì vậy bạn có thể thực hiện LeftJoinbằng cách sử dụng Joincú pháp (ít nhất là trong ký hiệu cuộc gọi phương thức):

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

Mã của tôi không làm gì khác hơn là thêm một GroupJoinvà một SelectManylệnh gọi vào cây biểu thức hiện tại. Tuy nhiên, nó trông khá phức tạp vì tôi phải tự xây dựng các biểu thức và sửa đổi cây biểu thức do người dùng chỉ định trong resultSelectortham số để giữ cho toàn bộ cây có thể dịch được bởi 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
Cảm ơn vì phần mở rộng này fero.
Fergers

Điều này vẫn tuyệt vời. Cảm ơn!
TheGeekYouNeed

1
Đã kiểm tra điều này trong .NET Framework 4.6.2 và nó hoạt động như mong đợi (tức là tạo ra một THAM GIA TRÁI OUTER). Tôi đang tự hỏi liệu nó có hoạt động trên .NET Core hay không. Cảm ơn.
Alexei

23

Hãy làm cho cuộc sống của bạn dễ dàng hơn (không sử dụng tham gia vào nhóm):

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
Tránh tham gia vào nhóm là một vấn đề quan điểm, nhưng đó chắc chắn là một ý kiến ​​xác đáng. Price = ugp.Pricecó thể thất bại nếu Pricelà một thuộc tính không thể nullable và phép nối bên trái không cho kết quả nào.

1
Đồng ý với điều trên, nhưng với nhiều hơn hai bảng, cách tiếp cận này dễ đọc và dễ bảo trì hơn nhiều.
Tomasz Skomra

1
Chúng tôi có thể kiểm tra xem ugp == NULLvà đặt giá trị mặc định cho Price.
Hp93

vừa hoàn hảo :)
MohammadHossein R

1
Tuyệt vời! Tôi thích giải pháp này cho tính dễ đọc. Ngoài ra, điều này làm cho nhiều lần tham gia hơn (tức là từ 3 bảng trở lên) dễ dàng hơn nhiều! Tôi đã sử dụng nó thành công cho 2 phép nối bên trái (tức là 3 bảng).
Jeremy Morren

4

Nếu bạn thích ký hiệu cuộc gọi phương thức, bạn có thể buộc nối trái bằng cách sử dụng SelectManykết hợp với DefaultIfEmpty. Ít nhất trên Entity Framework 6 đánh SQL Server. Ví dụ:

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

(Lưu ý rằng đó MyTable2.Col2là một cột của loại int). SQL được tạo sẽ giống như sau:

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]

Đối với tôi, điều này đang tạo ra một số truy vấn cực kỳ chậm với "ÁP DỤNG CHÉO" trong đó.
Meekohi

2

Đối với 2 lần tham gia bên trái trở lên (người tạo tham gia bên trái và người khởi tạo người dùng)

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

Tôi đã có thể thực hiện việc này bằng cách gọi DefaultIfEmpty () trên mô hình chính. Điều này cho phép tôi rời tham gia trên các thực thể được tải chậm, có vẻ dễ đọc hơn đối với tôi:

        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
Ở đây, bạn không cần .DefaultIfEmpty()gì cả: nó chỉ ảnh hưởng đến những gì xảy ra khi db.Complainstrống. db.Complains.Where(...).OrderBy(...).Select(x => new { ..., MemberName = x.Member != null ? x.Member.Name : string.Empty, ... }), nếu không có bất kỳ .DefaultIfEmpty(), sẽ thực hiện một phép nối bên trái (giả sử thuộc Membertính được đánh dấu là tùy chọn).

1

Nếu Nhóm người dùng có mối quan hệ từ một đến nhiều với bảng UserGroupPrices, thì trong EF, khi mối quan hệ được xác định trong mã như:

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

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

Bạn có thể kéo tập kết quả nối bên trái bằng cách đơn giản sau:

var list = db.UserGroupDbSet.ToList();

giả sử DbSet của bạn cho bảng bên trái là UserGroupDbSet, sẽ bao gồm UserGrpPriceList, là danh sách tất cả các bản ghi được liên kết từ bảng bên phải.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.