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.

Làm thế nào để join bằng cách sử dụng Key Composite

Ví dụ này cho thấy làm thế nào để thực hiện join các hoạt động mà bạn muốn sử dụng nhiều hơn một khóa chính để xác định một trận đấu. Điều này được thực hiện bằng cách sử dụng một khóa composite. Bạn tạo một khóa composite như là một loại anonymous hoặc tên gõ với các giá trị mà bạn muốn so sánh. Nếu biến query sẽ được chuyển qua các biên giới, phương pháp sử dụng một loại có tên overrides Equals và GetHashCode cho khoá. Tên của các thuộc tính, và thứ tự mà chúng xảy ra, phải được giống hệt nhau trong mỗi khóa.

Ví dụ sau đây cho thấy làm thế nào để sử dụng một khóa composite để join dữ liệu từ ba bảng:

var query = from o in db.Orders from p in db.Products
       join d in db.OrderDetails on new {o.OrderID, p.ProductID} equals new {d.OrderID,
              d.ProductID} into details from d in details
       select new {o.OrderID, p.ProductID, d.UnitPrice};

Các khóa composite phụ thuộc vào tên của các thuộc tính trong các khóa, và thứ tự mà chúng xảy ra. Nếu các thuộc tính trong chuỗi nguồn không có tên giống nhau, bạn phải gán tên mới trong các khóa. Ví dụ, nếu bảng Orders và bảng OrderDetails từng được sử dụng tên khác nhau cho các cột của họ, bạn có thể tạo ra phím composite bằng cách chỉ định tên giống hệt nhau trong các loại anonymous:

join...on new {Name = o.CustomerName, ID = o.CustID} equals
        new {Name = d.CustName, ID = d.CustID }