다음을 통해 공유


join 절 (C# 참조)

이 절을 join 사용하여 개체 모델에 직접 관계가 없는 여러 소스 시퀀스의 요소를 연결합니다. 유일한 요구 사항은 각 원본의 요소가 같음을 위해 비교할 수 있는 일부 값을 공유한다는 것입니다. 예를 들어 식품 유통업체에는 특정 제품의 공급업체 목록과 구매자 목록이 있을 수 있습니다. 절을 join 사용하여 동일한 지정된 지역에 있는 해당 제품의 공급업체 및 구매자 목록을 만들 수 있습니다.

C# 언어 참조는 가장 최근에 릴리스된 C# 언어 버전을 문서화합니다. 또한 예정된 언어 릴리스의 공개 미리 보기 기능에 대한 초기 설명서도 포함되어 있습니다.

설명서는 언어의 마지막 세 버전 또는 현재 공개 미리 보기에서 처음 도입된 기능을 식별합니다.

팁 (조언)

C#에서 기능이 처음 도입된 시기를 찾으려면 C# 언어 버전 기록에 대한 문서를 참조하세요.

join 은 두 소스 시퀀스를 입력으로 사용합니다. 각 시퀀스의 요소는 다른 시퀀스의 해당 속성과 비교할 수 있는 속성이거나 포함되어야 합니다. 이 절은 join 특수 equals 키워드를 사용하여 지정된 키를 같음으로 비교합니다. 절이 join 수행하는 모든 조인은 등호입니다. 절 출력의 join 모양은 수행 중인 특정 조인 유형에 따라 달라집니다. 다음 목록에서는 가장 일반적인 세 가지 조인 유형을 보여 있습니다.

  • 내부 조인
  • 그룹 참여
  • 왼쪽 외부 조인

내부 조인

다음 예제에서는 간단한 내부 동등 조인을 보여줍니다. 이 쿼리는 "product name/category" 쌍의 플랫 시퀀스를 생성합니다. 동일한 범주 문자열이 여러 요소에 나타납니다. 일치하는 products요소가 categories 없으면 해당 범주가 결과에 나타나지 않습니다.

var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

자세한 내용은 내부 조인 수행을 참조하세요.

그룹 참여

join 식이 있는 into 절을 그룹 조인이라고 합니다.

var innerGroupJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };

그룹 조인은 왼쪽 소스 시퀀스의 요소를 오른쪽 소스 시퀀스의 하나 이상의 일치하는 요소와 연결하는 계층적 결과 시퀀스를 생성합니다. 그룹 조인은 관계형 용어와 동일하지 않습니다. 기본적으로 개체 배열의 시퀀스입니다.

오른쪽 소스 시퀀스의 요소가 왼쪽 소스의 요소와 일치하지 않는 경우 절은 join 해당 항목에 대해 빈 배열을 생성합니다. 따라서 결과 시퀀스가 그룹으로 구성된다는 점을 제외하고, 그룹 조인은 기본적으로 내부 동등 조인입니다.

그룹 조인의 결과만 선택하면 항목에 액세스할 수 있지만 일치하는 키를 식별할 수는 없습니다. 따라서 이전 예제와 같이 키 이름도 있는 새 형식으로 그룹 조인의 결과를 선택하는 것이 일반적으로 더 유용합니다.

물론 그룹 조인의 결과를 다른 하위 쿼리의 생성기로 사용할 수도 있습니다.

var innerGroupJoinQuery2 =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
    where prod2.UnitPrice > 2.50M
    select prod2;

자세한 내용은 그룹화된 조인 수행을 참조하세요.

왼쪽 외부 조인

왼쪽 외부 조인에서 쿼리는 오른쪽 시퀀스에 일치하는 요소가 없더라도 왼쪽 소스 시퀀스의 모든 요소를 반환합니다. LINQ에서 왼쪽 외부 조인을 수행하려면 그룹 조인과 함께 메서드를 사용하여 DefaultIfEmpty 왼쪽 요소에 일치하는 항목이 없는 경우 생성할 기본 오른쪽 요소를 지정합니다. 모든 참조 형식의 기본값으로 사용 null 하거나 사용자 정의 기본 형식을 지정할 수 있습니다. 다음 예제에서는 사용자 정의 기본 형식이 표시됩니다.

var leftOuterJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
    select new { CatName = category.Name, ProdName = item.Name };

자세한 내용은 왼쪽 외부 조인 수행을 참조하세요.

등가 연산자

join은 동등 조인을 수행합니다. 즉, 두 키의 같음만 기준으로 일치를 기반으로 할 수 있습니다. "보다 큼" 또는 "같지 않음"과 같은 다른 유형의 비교는 지원되지 않습니다. 모든 조인이 동일 조인임을 명확히 하기 위해 join 절은 equals 연산자 대신 == 키워드를 사용합니다. 키워드는 오직 equals 절에서만 사용할 수 있으며, 몇 가지 중요한 측면에서 join 연산자와 다릅니다. 문자열 equals 을 비교할 때 값별로 비교할 오버로드가 있고 연산 == 자는 참조 같음을 사용합니다. 비교의 양쪽에 동일한 문자열 변수가 equals 있고 == 동일한 결과에 도달하면 true입니다. 프로그램이 두 개 이상의 동등한 문자열 변수를 선언할 때 컴파일러는 모두 동일한 위치에 저장하기 때문입니다. 이를 인턴팅이라고합니다. 또 다른 중요한 차이점은 null 비교입니다. null equals nullequals 연산자로 평가할 때는 false로 평가되고, == 연산자로 평가할 때는 true로 평가됩니다. 마지막으로 범위 지정 동작은 다릅니다. 즉 equals, 왼쪽 키는 외부 소스 시퀀스를 사용하고 오른쪽 키는 내부 소스를 사용합니다. 외부 원본은 왼쪽 equals 의 범위에만 있고 내부 소스 시퀀스는 오른쪽 범위에만 있습니다.

비동등 조인

여러 from 절을 사용하여 쿼리에 독립적으로 새 시퀀스를 도입하여 비 등호, 교차 조인 및 기타 사용자 지정 조인 작업을 수행할 수 있습니다. 자세한 내용은 사용자 지정 조인 작업 수행을 참조하세요.

개체 컬렉션과 관계형 테이블의 조인

LINQ 쿼리 식에서는 개체 컬렉션에 대한 조인 작업을 수행합니다. 두 관계형 테이블과 정확히 동일한 방식으로 개체 컬렉션을 조인할 수 없습니다. LINQ에서는 두 소스 시퀀스에 관계가 없는 경우에만 명시적 join 절이 필요합니다. LINQ to SQL로 작업하는 경우 개체 모델은 외래 키 테이블을 기본 테이블의 속성으로 나타냅니다. 예를 들어 Northwind 데이터베이스에서 Customer 테이블은 Orders 테이블과 외래 키 관계를 맺습니다. 테이블을 개체 모델에 매핑할 때 Customer 클래스에는 Orders 해당 Customer와 연결된 컬렉션 Orders 이 포함된 속성이 있습니다. 실제로 조인은 이미 완료되었습니다.

LINQ to SQL 컨텍스트에서 관련 테이블을 쿼리하는 방법에 대한 자세한 내용은 방법: 데이터베이스 관계 매핑을 참조하세요.

복합 키

복합 키를 사용하여 여러 값의 같음을 테스트할 수 있습니다. 자세한 내용은 복합 키를 사용한 조인을 참조하세요. 절에서 group 복합 키를 사용할 수도 있습니다.

예시

다음 예제에서는 동일한 일치 키를 사용하여 동일한 데이터 원본에서 내부 조인, 그룹 조인 및 왼쪽 외부 조인의 결과를 비교합니다. 콘솔 디스플레이의 결과를 명확히 하기 위해 이러한 예제에 몇 가지 추가 코드가 추가됩니다.

class JoinDemonstration
{
    #region Data

    class Product
    {
        public required string Name { get; init; }
        public required int CategoryID { get; init; }
    }

    class Category
    {
        public required string Name { get; init; }
        public required int ID { get; init; }
    }

    // Specify the first data source.
    List<Category> categories =
    [
        new Category {Name="Beverages", ID=001},
        new Category {Name="Condiments", ID=002},
        new Category {Name="Vegetables", ID=003},
        new Category {Name="Grains", ID=004},
        new Category {Name="Fruit", ID=005}
    ];

    // Specify the second data source.
    List<Product> products =
    [
      new Product {Name="Cola",  CategoryID=001},
      new Product {Name="Tea",  CategoryID=001},
      new Product {Name="Mustard", CategoryID=002},
      new Product {Name="Pickles", CategoryID=002},
      new Product {Name="Carrots", CategoryID=003},
      new Product {Name="Bok Choy", CategoryID=003},
      new Product {Name="Peaches", CategoryID=005},
      new Product {Name="Melons", CategoryID=005},
    ];
    #endregion

    static void Main(string[] args)
    {
        JoinDemonstration app = new JoinDemonstration();

        app.InnerJoin();
        app.GroupJoin();
        app.GroupInnerJoin();
        app.GroupJoin3();
        app.LeftOuterJoin();
        app.LeftOuterJoin2();
    }

    void InnerJoin()
    {
        // Create the query that selects
        // a property from each element.
        var innerJoinQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID
           select new { Category = category.ID, Product = prod.Name };

        Console.WriteLine("InnerJoin:");
        // Execute the query. Access results
        // with a simple foreach statement.
        foreach (var item in innerJoinQuery)
        {
            Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
        }
        Console.WriteLine($"InnerJoin: {innerJoinQuery.Count()} items in 1 group.");
        Console.WriteLine(System.Environment.NewLine);
    }

    void GroupJoin()
    {
        // This is a demonstration query to show the output
        // of a "raw" group join. A more typical group join
        // is shown in the GroupInnerJoin method.
        var groupJoinQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           select prodGroup;

        // Store the count of total items (for demonstration only).
        int totalItems = 0;

        Console.WriteLine("Simple GroupJoin:");

        // A nested foreach statement is required to access group items.
        foreach (var prodGrouping in groupJoinQuery)
        {
            Console.WriteLine("Group:");
            foreach (var item in prodGrouping)
            {
                totalItems++;
                Console.WriteLine("   {0,-10}{1}", item.Name, item.CategoryID);
            }
        }
        Console.WriteLine($"Unshaped GroupJoin: {totalItems} items in {groupJoinQuery.Count()} unnamed groups");
        Console.WriteLine(System.Environment.NewLine);
    }

    void GroupInnerJoin()
    {
        var groupJoinQuery2 =
            from category in categories
            orderby category.ID
            join prod in products on category.ID equals prod.CategoryID into prodGroup
            select new
            {
                Category = category.Name,
                Products = from prod2 in prodGroup
                           orderby prod2.Name
                           select prod2
            };

        //Console.WriteLine("GroupInnerJoin:");
        int totalItems = 0;

        Console.WriteLine("GroupInnerJoin:");
        foreach (var productGroup in groupJoinQuery2)
        {
            Console.WriteLine(productGroup.Category);
            foreach (var prodItem in productGroup.Products)
            {
                totalItems++;
                Console.WriteLine("  {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
            }
        }
        Console.WriteLine($"GroupInnerJoin: {totalItems} items in {groupJoinQuery2.Count()} named groups");
        Console.WriteLine(System.Environment.NewLine);
    }

    void GroupJoin3()
    {

        var groupJoinQuery3 =
            from category in categories
            join product in products on category.ID equals product.CategoryID into prodGroup
            from prod in prodGroup
            orderby prod.CategoryID
            select new { Category = prod.CategoryID, ProductName = prod.Name };

        //Console.WriteLine("GroupInnerJoin:");
        int totalItems = 0;

        Console.WriteLine("GroupJoin3:");
        foreach (var item in groupJoinQuery3)
        {
            totalItems++;
            Console.WriteLine($"   {item.ProductName}:{item.Category}");
        }

        Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
        Console.WriteLine(System.Environment.NewLine);
    }

    void LeftOuterJoin()
    {
        // Create the query.
        var leftOuterQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });

        // Store the count of total items (for demonstration only).
        int totalItems = 0;

        Console.WriteLine("Left Outer Join:");

        // A nested foreach statement  is required to access group items
        foreach (var prodGrouping in leftOuterQuery)
        {
            Console.WriteLine("Group:");
            foreach (var item in prodGrouping)
            {
                totalItems++;
                Console.WriteLine("  {0,-10}{1}", item.Name, item.CategoryID);
            }
        }
        Console.WriteLine($"LeftOuterJoin: {totalItems} items in {leftOuterQuery.Count()} groups");
        Console.WriteLine(System.Environment.NewLine);
    }

    void LeftOuterJoin2()
    {
        // Create the query.
        var leftOuterQuery2 =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           from item in prodGroup.DefaultIfEmpty()
           select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };

        Console.WriteLine($"LeftOuterJoin2: {leftOuterQuery2.Count()} items in 1 group");
        // Store the count of total items
        int totalItems = 0;

        Console.WriteLine("Left Outer Join 2:");

        // Groups have been flattened.
        foreach (var item in leftOuterQuery2)
        {
            totalItems++;
            Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
        }
        Console.WriteLine($"LeftOuterJoin2: {totalItems} items in 1 group");
    }
}
/*Output:

InnerJoin:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Peaches   5
Melons    5
InnerJoin: 8 items in 1 group.


Unshaped GroupJoin:
Group:
    Cola      1
    Tea       1
Group:
    Mustard   2
    Pickles   2
Group:
    Carrots   3
    Bok Choy  3
Group:
Group:
    Peaches   5
    Melons    5
Unshaped GroupJoin: 8 items in 5 unnamed groups


GroupInnerJoin:
Beverages
    Cola       1
    Tea        1
Condiments
    Mustard    2
    Pickles    2
Vegetables
    Bok Choy   3
    Carrots    3
Grains
Fruit
    Melons     5
    Peaches    5
GroupInnerJoin: 8 items in 5 named groups


GroupJoin3:
    Cola:1
    Tea:1
    Mustard:2
    Pickles:2
    Carrots:3
    Bok Choy:3
    Peaches:5
    Melons:5
GroupJoin3: 8 items in 1 group


Left Outer Join:
Group:
    Cola      1
    Tea       1
Group:
    Mustard   2
    Pickles   2
Group:
    Carrots   3
    Bok Choy  3
Group:
    Nothing!  4
Group:
    Peaches   5
    Melons    5
LeftOuterJoin: 9 items in 5 groups


LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Nothing!  4
Peaches   5
Melons    5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/

비고

join 뒤에 into 는 없는 절이 메서드 호출로 Join 변환됩니다. join 뒤에 이어지는 into 절은 메서드 호출로 GroupJoin 변환됩니다.

참고하십시오