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 }

Làm thế nào để xử lý giá trị Null trong biểu thức truy vấn

 

Ví dụ này cho thấy làm thế nào để xử lý các giá trị null có thể có trong bộ sưu tập nguồn. Một bộ sưu tập các đối tượng đó như là một IEnumerable<T> có thể chứa các yếu tố có giá trị null. Nếu nguồn là một bộ sưu tập vô giá trị hoặc có một phần tử có giá trị null, và query của bạn không xử lý các giá trị null, NullReferenceException sẽ được đẩy ra ngoài khi bạn thực hiện câu truy vấn.

Bạn có thể bảo vệ mã để tránh một ngoại lệ tham chiếu null như trong ví dụ sau đây:

 

var query1 =  from c in categories
       where c != null
       join p in products on c.ID equals
               (p == null ? null : p.CategoryID)
       select new { Category = c.Name, Name = p.Name };

 

Trong ví dụ trước, các nơi mệnh đề lọc ra tất cả các yếu tố null trong thứ tự danh mục. Kỹ thuật này được độc lập của việc kiểm tra null trong mệnh đề join. Các biểu thức điều kiện với null trong ví dụ này hoạt động bởi vì Products.CategoryID là kiểu int? đó là viết tắt cho Nullable<int>.

Trong một mệnh đề join, nếu chỉ có một trong các phím so sánh là một loại giá trị nullable, bạn có thể bỏ các kia với một loại nullable trong biểu thức query. Trong ví dụ sau đây, giả sử là EmployeeID là một cột chứa giá trị kiểu int?:

 

void TestMethod(Northwind db)
{
       var query = from o in db.Orders
              join e in db.Employees
                     on o.EmployeeID equals (int?)e.EmployeeID
              select new { o.OrderID, e.FirstName };
}