Làm thế nào để sử dụng Left Outer Joins

Một left outer join là một join, trong đó mỗi phần tử của các bộ sưu tập đầu tiên được t

class Person
{
       public string FirstName { get; set; }
       public string LastName { get; set; }
}

class Pet
{
       public string Name { get; set; }
       public Person Owner { get; set; }
}


public static void LeftOuterJoinExample()
{
       Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
       Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
       Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
       Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
       Pet barley = new Pet { Name = "Barley", Owner = terry };
       Pet boots = new Pet { Name = "Boots", Owner = terry };
       Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
       Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
       Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
       // Create two lists.
       List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
       List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };
       var query = from person in people
              join pet in pets on person equals pet.Owner into gj
              from subpet in gj.DefaultIfEmpty()
              select new { 
                      person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name)   
              };
       foreach (var v in query)
       {
              Console.WriteLine("{0,-15}{1}", v.FirstName + ":", v.PetName);
       }
}
// This code produces the following output:
//
// Magnus: --- > Daisy
// Terry: --- > Barley
// Terry: --- >  Boots
// Terry: --- > Blue Moon
// Charlotte: --- > Whiskers
// Arlene: --- >
rả về, bất kể nó có bất kỳ yếu tố tương quan trong bộ sưu tập thứ hai. Bạn có thể sử dụng LINQ để thực hiện một left outer join bằng cách gọi DefaultIfEmpty kết quả của một group join.

Ví dụ sau đây cho thấy làm thế nào để sử dụng Methos DefaultIfEmpty kết quả của một group join để thực hiện một left outer join.

Bước đầu tiên trong việc tạo ra một left outer join trong hai bộ sưu tập là để thực hiện một bên join bằng cách sử dụng một group join. Trong ví dụ này, danh sách các đối tượng Person inner join vào danh sách các đối tượng PET dựa trên một Person object phù hợp với Pet.Owner.

Bước thứ hai là để bao gồm mỗi yếu tố của bộ sưu tập (left) đầu tiên trong tập hợp kết quả ngay cả khi không có yếu tố phù hợp trong bộ sưu tập phải. Điều này được thực hiện bằng cách gọi DefaultIfEmpty trên mỗi chuỗi kết hợp các yếu tố từ các group join. Trong ví dụ này, DefaultIfEmpty được gọi vào mỗi chuỗi các đối tượng phù hợp với PET. Nó trả về một bộ sưu tập có chứa một giá trị, mặc định duy nhất nếu các trình tự của các đối tượng phù hợp PET trống cho bất kỳ đối tượng Person, qua đó đảm bảo rằng kết quả mỗi đối tượng Person được đại diện trong bộ sưu tập.

 

Làm thế nào để sắp xếp các kết quả của một Clause join

 

Ví dụ này cho thấy làm thế nào để sắp xếp các kết quả của một hoạt động join. Lưu ý rằng các lệnh được thực hiện sau khi join. Mặc dù bạn có thể sử dụng một mệnh đề orderby với một hoặc nhiều trình tự mã nguồn trước khi join, nhìn chung chúng takhông giới thiệu nó. Một số nhà cung cấp LINQ có thể không có duy trì lệnh sau khi join.

Truy vấn này tạo ra một group join, và sau đó sắp xếp các group dựa trên các yếu tố thể loại, mà vẫn còn trong phạm vi. Bên trong bộ khởi tạo kiểu anonymous, một sub query orhers tất cả các yếu tố kết hợp từ các chuỗi sản phẩm.

class HowToOrderJoins
{
    #region Data
    class Product
    {
        public string Name { get; set; }
        public int CategoryID { get; set; }
    }
    class Category
    {
        public string Name { get; set; }
        public int ID { get; set; }
    }
    // Specify the first data source.
    List<Category> categories = new List<Category>()
       {
               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 List<Product>()
       {
               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()
    {
        HowToOrderJoins app = new HowToOrderJoins();
        app.OrderJoin1();
        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }


    void OrderJoin1()
    {
        var groupJoinQuery2 = from category in categories
                              join prod in products on category.ID equals prod.CategoryID into prodGroup
                              orderby category.Name
                              select new
                              {
                                  Category = category.Name,
                                  Products = from prod2 in prodGroup
                                             orderby prod2.Name
                                             select prod2
                              };
        foreach (var productGroup in groupJoinQuery2)
        {
            Console.WriteLine(productGroup.Category);
            foreach (var prodItem in productGroup.Products)
            {
                Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
            }
        }
    }
    /* Output : 
        Beverages
        Cola 1
        Tea 1
        Condiments
        Mustard 2
        Pickles 2
        Fruit
        Melons 5
        Peaches 5
        Grains
        Vegetables
        Bok Choy 3
        Carrots 3
     */
}

 

Làm thế nào nhóm Kết quả của contiguous keys

Ví dụ sau đây cho thấy làm thế nào để các nguyên tố nhóm thành nhiều phần đại diện cho subsequences của các contiguous keys. Ví dụ, giả sử rằng bạn có các trình tự sau đây của các cặp khóa có giá trị:

Key : A, A, A, B, C, A, B, B

Value : We, think, that, Linq, is, really, cool, !

Các nhóm sau đây sẽ được tạo ra theo thứ tự này:

  1. We, think, that
  2. Linq
  3. is
  4. really
  5. cool, !

Giải pháp này được thực hiện như là một phương pháp mở rộng đó là thread an toàn và trả về kết quả của nó một cách trực tuyến. Nói cách khác, nó tạo ra nhóm của nó khi nó di chuyển qua các dãy nguồn. Không giống như các nhà điều hành nhóm hay orderby, nó có thể bắt đầu trở lại nhóm người gọi trước khi tất cả các trình tự đã được đọc.

Chủ đề an toàn được thực hiện bằng cách làm một bản sao của từng nhóm hoặc từng đoạn theo trình tự được lặp lại nguồn, như được giải thích trong các ý kiến mã nguồn. Nếu dãy nguồn có một chuỗi lớn các mục liên tiếp, ngôn ngữ chung có thể bỏ một OutOfMemoryException.

using System;
using System.Collections.Generic;
using System.Linq;
namespace ChunkIt
{
    // Static class to contain the extension methods.
    public static class MyExtensions
    {
        public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
        }
        public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this 
                         IEnumerable<TSource> source, Func<TSource, TKey> keySelector,
                   IEqualityComparer<TKey> comparer)
        {
            // Flag to signal end of source sequence.
            const bool noMoreSourceElements = true;
            // Auto-generated iterator for the source array.
            var enumerator = source.GetEnumerator();
            // Move to the first element in the source sequence.
            if (!enumerator.MoveNext()) yield break;
            Chunk<TKey, TSource> current = null;
            while (true)
            {
                // Get the key for the current Chunk. The source iterator will churn through
                // the source sequence until it finds an element with a key that doesn't match.
                var key = keySelector(enumerator.Current);
                // Make a new Chunk (group) object that initially has one 
                // GroupItem, which is a copy of the current source element.
                current = new Chunk<TKey, TSource>(key, enumerator, value =>
                                comparer.Equals(key, keySelector(value)));

                yield return current;
                if (current.CopyAllChunkElements() == noMoreSourceElements)
                {
                    yield break;
                }
            }
        }
        class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
        {
            // has a reference to the next ChunkItem in the list.
            class ChunkItem
            {
                public ChunkItem(TSource value)
                {
                    Value = value;
                }
                public readonly TSource Value;
                public ChunkItem Next = null;
            }
            // The value that is used to determine matching elements
            private readonly TKey key;
            // Stores a reference to the enumerator for the source sequence
            private IEnumerator<TSource> enumerator;
            // A reference to the predicate that is used to compare keys.
            private Func<TSource, bool> predicate;
            // Stores the contents of the first source element that belongs with this chunk.
            private readonly ChunkItem head;
            // End of the list. It is repositioned each time a new. ChunkItem is added.
            private ChunkItem tail;
            // Flag to indicate the source iterator has reached the end of the source sequence.
            internal bool isLastSourceElement = false;
            // Private object for thread syncronization
            private object m_Lock;
            // REQUIRES: enumerator != null && predicate != null
            public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
            {
                this.key = key;
                this.enumerator = enumerator;
                this.predicate = predicate;
                // A Chunk always contains at least one element.
                head = new ChunkItem(enumerator.Current);
                // The end and beginning are the same until the list contains > 1 elements.
                tail = head;
                m_Lock = new object();
            }
            // Indicates that all chunk elements have been copied to the list of ChunkItems,
            // and the source enumerator is either at the end, or else on an element with a new key.
            // the tail of the linked list is set to null in the CopyNextChunkElement method if the
            // key of the next element does not match the current chunk's key, or there are no more  
            // elements in the source.
            private bool DoneCopyingChunk { get { return tail == null; } }
            // Adds one ChunkItem to the current group 
            // REQUIRES: !DoneCopyingChunk && lock(this)
            private void CopyNextChunkElement()
            {
                // Try to advance the iterator on the source sequence.
                // If MoveNext returns false we are at the end, 
                // and isLastSourceElement is set to true                
                isLastSourceElement = !enumerator.MoveNext();
                // If we are (a) at the end of the source, or (b) at the end of the current chunk
                // then null out the enumerator and predicate for reuse with the next chunk.
                if (isLastSourceElement || !predicate(enumerator.Current))
                {
                    enumerator = null;
                    predicate = null;
                }
                else
                {
                    tail.Next = new ChunkItem(enumerator.Current);
                }
                // tail will be null if we are at the end of the chunk elements
                // This check is made in DoneCopyingChunk.
                tail = tail.Next;
            }
            internal bool CopyAllChunkElements()
            {
                while (true)
                {
                    lock (m_Lock)
                    {
                        if (DoneCopyingChunk)
                        {
                            // If isLastSourceElement is false,  it signals to the 
                            // outer iterator  to continue iterating.
                            return isLastSourceElement;
                        }
                        else
                        {
                            CopyNextChunkElement();
                        }
                    }
                }
            }
            public TKey Key { get { return key; } }
            // Invoked by the inner foreach loop. This method stays just one step ahead
            // of the client requests. It adds the next element of the chunk only after
            // the clients requests the last element in the list so far.
            public IEnumerator<TSource> GetEnumerator()
            {
                //Specify the initial element to enumerate.
                ChunkItem current = head;
                // There should always be at least one ChunkItem in a Chunk.
                while (current != null)
                {
                    // Yield the current item in the list.
                    yield return current.Value;
                    // Copy the next item from the source sequence,
                    // if we are at the end of our local list.
                    lock (m_Lock)
                    {
                        if (current == tail)
                        {
                            CopyNextChunkElement();
                        }
                    }
                    // Move to the next ChunkItem in the list.
                    current = current.Next;
                }
            }
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

        }

    }
    // A simple named type is used for easier viewing in the debugger. Anonymous types
    // work just as well with the ChunkBy operator.
    public class KeyValPair
    {
        public string Key { get; set; }
        public string Value { get; set; }
    }
    class Program
    {
        // The source sequence.
        public static IEnumerable<KeyValPair> list;
        // Query variable declared as class member to be available
        // on different threads.
        static IEnumerable<IGrouping<string, KeyValPair>> query;
        static void Main(string[] args)
        {
            // Initialize the source sequence with an array initializer.
            list = new[]{
                          new KeyValPair{ Key = "A", Value = "We" },
                          new KeyValPair{ Key = "A", Value = "Think" },
                          new KeyValPair{ Key = "A", Value = "That" },
                          new KeyValPair{ Key = "B", Value = "Linq" },
                          new KeyValPair{ Key = "C", Value = "Is" },
                          new KeyValPair{ Key = "A", Value = "Really" },
                          new KeyValPair{ Key = "B", Value = "Cool" },
                          new KeyValPair{ Key = "B", Value = "!" }
                    };
            // Create the query by using our user-defined query operator.
            query = list.ChunkBy(p => p.Key);
            // ChunkBy returns IGrouping objects, therefore a nested
            // foreach loop is required to access the elements in each "chunk".
            foreach (var item in query)
            {
                Console.WriteLine("Group key = {0}", item.Key);
                foreach (var inner in item)
                {
                    Console.WriteLine("\t{0}", inner.Value);
                }
            }
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
    }
}

Để sử dụng Methos mở rộng trong dự án của bạn, sao chép các MyExtensions class Static vào một tập tin mã nguồn mới hoặc hiện tại và nếu nó là cần thiết, thêm một chỉ thị bằng cách sử dụng cho các namespace nơi mà nó được đặt.

Làm thế nào để thực hiện một Subquery vào một hoạt động Nhóm

Chủ đề này cho thấy hai cách khác nhau để tạo ra một truy vấn mà đơn đặt hàng dữ liệu nguồn thành các nhóm, và sau đó thực hiện một subquery trong mỗi nhóm riêng. Các kỹ thuật cơ bản trong mỗi ví dụ là nhóm các yếu tố nguồn bằng cách sử dụng một sự tiếp nối tên newGroup, và sau đó tạo ra một subquery mới chồng lên newGroup. Subquery này được chạy với từng nhóm mới được tạo ra bởi các truy vấn bên ngoài.

Lưu ý rằng trong ví dụ cụ thể kết quả cuối cùng không phải là một nhóm.

public void QueryMax()
{

       var queryGroupMax = from student in students
              group student by student.Year into studentGroup
              select new
              {
                      Level = studentGroup.Key,
                      HighestScore = (from student2 in studentGroup
                      select student2.ExamScores.Average()).Max()
              };
       int count = queryGroupMax.Count();
       Console.WriteLine("Number of groups = {0}", count);
       foreach (var item in queryGroupMax)
       {
               Console.WriteLine(" {0} Highest Score={1}", item.Level, item.HighestScore);
       }
}

Làm thế nào để tạo ra các nhóm lồng nhau trong một biểu thức truy vấn LINQ

Ví dụ sau đây cho thấy làm thế nào để tạo ra các nhóm lồng nhau trong một biểu thức truy vấn LINQ. Mỗi nhóm được tạo ra theo năm học hoặc trình độ lớp sau đó được chia nhỏ thành các nhóm dựa trên tên của cá nhân.

public void QueryNestedGroups()
{

         var queryNestedGroups = from student in students  
                    group student by student.Year into newGroup1
                    from newGroup2 in
                           (from student in newGroup1  group student by student.LastName)
                    group newGroup2 by newGroup1.Key;

         // Three nested foreach loops are required to iterate 
         // over all elements of a grouped group. Hover the mouse 
         // cursor over the iteration variables to see their actual type.
         foreach (var outerGroup in queryNestedGroups)
         {
                 Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
                 foreach (var innerGroup in outerGroup)
                {
                        Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
                        foreach (var innerGroupElement in innerGroup)
                       {
                               Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
                       }
               }
        }
}

Lưu ý rằng ba vòng lặp foreach lồng nhau được yêu cầu phải duyệt qua các yếu tố bên trong của một nhóm lồng nhau.