LINQ查詢運算式 - inner join、left join、Cartesian Product

 

完整的資料結構範例放在文章的最下面,

其他說明則以 main function 為主。

 

七、join(inner join)

1、根據專案名稱列出所有問題單的摘要與專案連絡 mail

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            SampleData data = new SampleData();

            var query = from defect in SampleData.AllDefects
                        join subscription in SampleData.AllSubscriptions
                        on defect.Project equals subscription.Project
                        select new { defect.Summary, subscription.EmailAddress };

            foreach (var item in query)
            {
                Console.WriteLine($"{item.Summary} - {item.EmailAddress}");
            }
        }
    }
}

執行結果為

說明:

a、其實架構還蠻像MSSQL的語法,只是on後面的判斷式是由 defect 與 subscription 範圍變數所帶出的,

另外其判斷式的等於符號已改成 equals 英文寫法。

b、on 後面判斷式的連結索引鍵位置不能亂放,

他是對等於區域變數的位置,如果順序對調會出錯。

c、當兩資料要 join 時,通常會將資料多的放在左邊,

資料少的放在 join 的右邊,左邊是循序存取區,右邊是記憶體緩衝區,

目的是儘量不占記憶體空間。

 

2、要對來源資料進行篩選,在 join 之前會來的更有效率

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            SampleData data = new SampleData();

            var query = from defect in SampleData.AllDefects
                        where defect.Status == Status.Accepted
                        join subscription in SampleData.AllSubscriptions
                        on defect.Project equals subscription.Project
                        select new { defect.Summary, subscription.EmailAddress };

            foreach (var item in query)
            {
                Console.WriteLine($"{item.Summary} - {item.EmailAddress}");
            }
        }
    }
}

執行結果為

 

3、承上結果相同,但對 defect 使用子查詢的寫法

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            SampleData data = new SampleData();

            var query = from subscription in SampleData.AllSubscriptions
                        join defect in (
                        from defect in SampleData.AllDefects
                        where defect.Status == Status.Accepted
                        select defect
                        )
                        on subscription.Project equals defect.Project
                        select new { defect.Summary, subscription.EmailAddress };

            foreach (var item in query)
            {
                Console.WriteLine($"{item.Summary} - {item.EmailAddress}");
            }
        }
    }
}

執行結果為

 

八、join...into(left join)

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            SampleData data = new SampleData();

            var query = from defect in SampleData.AllDefects
                        join subscription in SampleData.AllSubscriptions
                        on defect.Project equals subscription.Project
                        into theInnerjoin
                        select new { Defect = defect, innerjoin = theInnerjoin };

            foreach (var entry in query)
            {
                foreach (var item in entry.innerjoin)
                {
                    Console.WriteLine($"{entry.Defect.Summary} - {item.EmailAddress}");
                }
            }
        }
    }
}

執行結果為

說明:

into 是用來把 inner join 後的結果投影到一個範圍變數,

一個 defect 有多筆的 subscription,而以 left join 效果來說,

以 defect 為基底,找出每一個 defect 所對應到多筆的 subscription (裡的 E-mail)。

註:雖然效果等同於 left join,但是其跟 T-SQL 做法很不一樣,很「程式化」的感覺。

 

九、from (Cartesian Product)

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            SampleData data = new SampleData();

            var query = from user in SampleData.AllUsers
                        from project in SampleData.AllProjects
                        select new { User = user, Project = project };

            foreach (var pair in query)
            {
                Console.WriteLine("{0}/{1}",
                                  pair.User.Name,
                                  pair.Project.Name);
            }
        }
    }
}

執行結果為

說明:

AllUsers 有六位,AllProjects 有三個,其 Cartesian Product 的效果為

將 AllUsers 與 AllProjects 作相乘動作,其共會有 6 * 3 = 18 種組合。

 

 

 

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {

        }
    }

    public class SampleData
    {
        static List<Project> projects;
        public static IEnumerable<Project> AllProjects
        {
            get { return projects; }
        }

        static List<NotificationSubscription> subscriptions;
        public static IEnumerable<NotificationSubscription> AllSubscriptions
        {
            get { return subscriptions; }
        }

        static List<Defect> defects;
        public static IEnumerable<Defect> AllDefects
        {
            get { return defects; }
        }

        static List<User> users;
        public static IEnumerable<User> AllUsers
        {
            get { return users; }
        }

        public static class Users
        {
            public static readonly User TesterTim = new User("Tim Trotter", UserType.Tester);
            public static readonly User TesterTara = new User("Tara Tutu", UserType.Tester);
            public static readonly User DeveloperDeborah = new User("Deborah Denton", UserType.Developer);
            public static readonly User DeveloperDarren = new User("Darren Dahlia", UserType.Developer);
            public static readonly User ManagerMary = new User("Mary Malcop", UserType.Manager);
            public static readonly User CustomerColin = new User("Colin Carton", UserType.Customer);
        }

        public static class Projects
        {
            public static readonly Project SkeetyMediaPlayer = new Project { Name = "Skeety Media Player" };
            public static readonly Project SkeetyTalk = new Project { Name = "Skeety Talk" };
            public static readonly Project SkeetyOffice = new Project { Name = "Skeety Office" };
        }

        public SampleData()
        {
            projects = new List<Project>
            {
                Projects.SkeetyMediaPlayer,
                Projects.SkeetyTalk,
                Projects.SkeetyOffice
            };

            subscriptions = new List<NotificationSubscription>
            {
                new NotificationSubscription { Project=Projects.SkeetyMediaPlayer, EmailAddress="media-bugs@skeetysoft.com" },
                new NotificationSubscription { Project=Projects.SkeetyTalk, EmailAddress="talk-bugs@skeetysoft.com" },
                new NotificationSubscription { Project=Projects.SkeetyOffice, EmailAddress="office-bugs@skeetysoft.com" },
                new NotificationSubscription { Project=Projects.SkeetyMediaPlayer, EmailAddress="theboss@skeetysoft.com"}
            };

            users = new List<User>
            {
                Users.TesterTim,
                Users.TesterTara,
                Users.DeveloperDeborah,
                Users.DeveloperDarren,
                Users.ManagerMary,
                Users.CustomerColin
            };

            defects = new List<Defect>
            {
                 new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(1),
                    CreatedBy = Users.TesterTim,
                    Summary = "MP3 files crash system",
                    Severity = Severity.Showstopper,
                    AssignedTo = Users.DeveloperDarren,
                    Status = Status.Accepted,
                    LastModified = May(23)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(6),
                    CreatedBy = Users.TesterTim,
                    Summary = "Installation is slow",
                    Severity = Severity.Trivial,
                    AssignedTo = Users.TesterTim,
                    Status = Status.Fixed,
                    LastModified = May(15)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(8),
                    CreatedBy = Users.DeveloperDarren,
                    Summary = "Subtitles only work in Welsh",
                    Severity = Severity.Major,
                    AssignedTo = Users.TesterTim,
                    Status = Status.Fixed,
                    LastModified = May(23)
                },

                new Defect
                {
                    Project = Projects.SkeetyTalk,
                    Created = May(9),
                    CreatedBy = Users.TesterTim,
                    Summary = "User interface should be more caramelly",
                    Severity = Severity.Trivial,
                    AssignedTo = Users.DeveloperDarren,
                    Status = Status.Created,
                    LastModified = May(9)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(12),
                    CreatedBy = Users.TesterTim,
                    Summary = "Play button points the wrong way",
                    Severity = Severity.Major,
                    AssignedTo = Users.TesterTim,
                    Status = Status.Fixed,
                    LastModified = May(17)
                },

                new Defect
                {
                    Project = Projects.SkeetyTalk,
                    Created = May(13),
                    CreatedBy = Users.TesterTim,
                    Summary = "Server crashes under heavy load (3 users)",
                    Severity = Severity.Major,
                    AssignedTo = Users.DeveloperDeborah,
                    Status = Status.Accepted,
                    LastModified = May(17)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(17),
                    CreatedBy = Users.TesterTim,
                    Summary = "Modern music sounds rubbish",
                    Severity = Severity.Trivial,
                    AssignedTo = Users.DeveloperDarren,
                    Status = Status.Created,
                    LastModified = May(17)
                },

                new Defect
                {
                    Project = Projects.SkeetyTalk,
                    Created = May(18),
                    CreatedBy = Users.TesterTim,
                    Summary = "Webcam makes me look bald",
                    Severity = Severity.Showstopper,
                    AssignedTo = Users.TesterTim,
                    Status = Status.Fixed,
                    LastModified = May(27)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(22),
                    CreatedBy = Users.TesterTim,
                    Summary = "DVD Easter eggs unavailable",
                    Severity = Severity.Trivial,
                    AssignedTo = Users.DeveloperDarren,
                    Status = Status.Created,
                    LastModified = May(22)
                },

                new Defect
                {
                    Project = Projects.SkeetyTalk,
                    Created = May(25),
                    CreatedBy = Users.TesterTim,
                    Summary = "Logs record confidential conversations",
                    Severity = Severity.Major,
                    AssignedTo = Users.DeveloperDarren,
                    Status = Status.Reopened,
                    LastModified = May(30)
                },

                new Defect
                {
                    Project = Projects.SkeetyMediaPlayer,
                    Created = May(30),
                    CreatedBy = Users.TesterTim,
                    Summary = "Network is saturated when playing WAV file",
                    Severity = Severity.Minor,
                    AssignedTo = Users.TesterTim,
                    Status = Status.Fixed,
                    LastModified = May(31)
                }
            };
        }

        public static DateTime May(int day)
        {
            return new DateTime(2010, 5, day);
        }
    }

    public class NotificationSubscription
    {
        /// <summary>
        /// Project for which this subscriber is notified
        /// </summary>
        public Project Project { get; set; }

        /// <summary>
        /// The address to send the notification to
        /// </summary>
        public string EmailAddress { get; set; }
    }

    public class Defect
    {
        public Project Project { get; set; }
        /// <summary>
        /// Which user is this defect currently assigned to? Should not be null until the status is Closed.
        /// </summary>
        public User AssignedTo { get; set; }
        public string Summary { get; set; }
        public Severity Severity { get; set; }
        public Status Status { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastModified { get; set; }
        public User CreatedBy { get; set; }
        public int ID { get; private set; }

        public Defect()
        {
            ID = StaticCounter.Next();
        }

        public override string ToString()
        {
            return string.Format("{0,2}: {1}\r\n    ({2:d}-{3:d}, {4}/{5}, {6} -> {7})",
                ID, Summary, Created, LastModified, Severity, Status, CreatedBy.Name,
                AssignedTo == null ? "n/a" : AssignedTo.Name);
        }
    }

    public static class StaticCounter
    {
        static int next = 1;

        public static int Next()
        {
            return next++;
        }
    }

    public class Project
    {
        public string Name { get; set; }

        public override string ToString()
        {
            return string.Format("Project: {0}", Name);
        }
    }

    public enum Severity : byte
    {
        Trivial,
        Minor,
        Major,
        Showstopper,
    }

    public enum Status : byte
    {
        /// <summary>
        /// Defect has been opened, but not verified as reproducible or an issue.
        /// </summary>
        Created,
        /// <summary>
        /// Defect has been verified as an issue requiring work.
        /// </summary>
        Accepted,
        /// <summary>
        /// Defect has been fixed in code, but not verified other than through developer testing.
        /// </summary>
        Fixed,
        /// <summary>
        /// Defect was fixed, but has now been reopened due to failing verification.
        /// </summary>
        Reopened,
        /// <summary>
        /// Defect has been fixed and tested; the fix is satisfactory.
        /// </summary>
        Closed,
    }

    public class User
    {
        public string Name { get; set; }
        public UserType UserType { get; set; }

        public User(string name, UserType userType)
        {
            Name = name;
            UserType = userType;
        }

        public override string ToString()
        {
            return string.Format("User: {0} ({1})", Name, UserType);
        }
    }

    public enum UserType : byte
    {
        Customer,
        Developer,
        Tester,
        Manager,
    }
}