Menu

LINQ – podstawy zapytań do obiektów

2 sierpnia 2017 - .NET, Podstawy

LINQ

Wprowadzenie

LINQ – ang. Language Integrated Query (zintegrowany język zapytań) – jest to potężna grupa narzędzi, służąca do operowania na zbiorach danych w języku C#. Mimo że podstawowym założeniem było umożliwienie przystępnego sposobu operowania na relacyjnych bazach dany, to w praktyce możemy operować na wielu rodzajach danych jak choćby obiektach przechowywanych w pamięci, plikach XML czy informacjach udostępnianych za pośrednictwem HTTP. Dzięki temu LINQ ma zastosowanie w praktycznie każdej aplikacji.

Do implementacji standardowego zbioru możliwości zapytań LINQ wymagane są biblioteki zwane operatorami LINQ. Każdy rodzaj danych wymaga ich odrębnej implementacji. Grupa operatorów przeznaczona do konkretnego typu informacji nosi nazwę dostawcy LINQ. Biblioteka .NET Framework posiada kilku wbudowanych dostawców takich jak np. LINQ to Objects, LINQ to SQL, LINQ to Entities. LINQ jest technologią rozszerzalną, więc nic nie stoi na przeszkodzie aby poszukać dodatkowych dostawców tworzonych przez inne firmy bądź społeczność.

Wyrażenia zapytań

Wyrażanie zapytań nieco przypomina te zastosowane w bazach danych, jednak ja wyżej zostało napisane, można ich używać z dowolnym dostawcą. Na potrzeby tego wpisu, będziemy korzystać z Linq to Object, ponieważ nie trzeba kombinować z połączeniami z bazą danych, a na przygotowanej liście będzie to działało dokładnie tak samo jakbyśmy odpytywali bazę danych. Przygotujmy sobie więc nasz testowy program. Przygotujemy dane dla testów oraz pojawi się pierwsze zapytanie LINQ. Nasze zapytanie przeszukuje cała listę Employees i zwraca tych dla których stanowisko to Administrator. Użyta pętla foreach wyświetli nam w konsoli imię, nazwisko i wynagrodzenie wyszukanych pracowników.

     class Program
    {
        static void Main(string[] args)
        {
            List<Employee> Employees = new List<Employee>()
            {
                new Employee("Mateusz", "Konieczny", Position.Administrator, 7500, new DateTime(2015,05,05)),
                new Employee("Łukasz", "Kowalski", Position.Developer, 9500, new DateTime(2000,04,12)),
                new Employee("Damian", "Baczyński", Position.Administrator, 11320, new DateTime(2003,09,1)),
                new Employee("Dominik", "Nieznany", Position.ApplicationSpecialist, 6589, new DateTime(2007,02,20)),
                new Employee("Jan", "Szczęsny", Position.ApplicationSpecialist, 8240, new DateTime(2011,08,28)),
                new Employee("Dawid", "Nieszczęsny", Position.Developer, 10000, new DateTime(1999,08,30)),
                new Employee("Tobiasz", "Topola", Position.Developer, 5000, new DateTime(2016,01,15)),
                new Employee("Anita", "Pałecka", Position.ApplicationSpecialist, 8500, new DateTime(2010,05,14)),
            };

            foreach (var employee in employees)
            {
                Console.WriteLine(String.Join(" ", new string[] {
                    employee.LastName,
                    employee.FirstName,
                    employee.Reward.ToString()
                }));
            }

            foreach (var employee in Employees)
            {
                if (employee.Position == Position.Administrator)
                {
                    Console.WriteLine(String.Join(" ", new string[] {
                    employee.LastName,
                    employee.FirstName,
                    employee.Reward.ToString()
                    }));
                }
            }
        }
    }

    public enum Position
    {
        Administrator, ApplicationSpecialist, Developer
    }

    public class Employee
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Position Position { get; set; }
        public double Reward { get; set; }
        public DateTime EmploymentDate { get; set; }

        public Employee(string firstName, string lastName, Position position, double reward, DateTime employmentDate)
        {
            FirstName = firstName;
            LastName = lastName;
            Position = position;
            Reward = reward;
            EmploymentDate = employmentDate;
        }
    }

Oczywiście jesteśmy w stanie wykonać to samo zadanie bez korzystania z LINQ co pokazuje przykład poniżej:

            foreach (var employee in Employees)
            {
                if ( employee.Position == Position.Administrator)
                {
                    Console.WriteLine(String.Join(" ", new string[] {
                    employee.LastName,
                    employee.FirstName,
                    employee.Reward.ToString()
                    }));
                }
            }

Można sobie zadać pytanie, po co w takim razie bawić się w używanie LINQ, skoro na pierwszy rzut oka widać, że naklepanych liter jest zdecydowanie mniej w przypadku z if’em?

Ważną zaletą jest oddzielenie kodu pobierające określone elementy jest wyraźnie oddzielony od kodu, który odpowiada za to co z nimi zrobić. W pierwszym przykładzie najpierw tworzymy kolekcję danych i na niej możemy wykonać dowolne operacje. W drugim przypadku te operacje są wymieszane ze sobą.

Może niekoniecznie w naszym przypadku ma to tak duże znaczenie, ale już działając na bazach danych dużo lepszym rozwiązaniem jest aby to baza zwróciła nam przefiltrowane wyniki, niż żebyśmy to sami robili w pętli.

Składowe zapytania

Analizując zapytanie LINQ można zauważyć, że składa się ono z trzech części:

  1. From – to od tego słowa zaczyna się całe zapytanie. Określa ono źródło zapytania oraz tak zwaną zmienną zakresu, której w dalszej części zapytania możemy używać jako reprezentant pojedynczego elementu.
  2. Where – klauzura ta jest opcjonalna. Jest ona użyta raz dla każdego pojedynczego elementu kolekcji. W zapytaniu można nie używać klauzury where, użyć jedną bądź też kilku. Ogólnie rzecz biorąc odpowiada ona za filtrowanie wyników. W naszym przykładnie interesowali nas tylko pracownicy na stanowisku administratora i takie obiekty dostaliśmy.
  3. Select – jest to ostatnia część zapytania. Wszystkie wyrażenia zapytań muszą się nią kończyć lub w niektórych przypadkach klauzurą group. Określa ona ostateczną postać elementów jakie mają zostać zwrócone. W naszym przypadku kolekcja employees będzie zawierała obiekty typu Employee, jednak nic nie stoi na przeszkodzie, żeby zwrócić obiekty innego typu np:
                IEnumerable<String> employeesName =
                    from employee in Employees
                    where employee.Position == Position.Administrator
                    select employee.LastName + " " + employee.FirstName;
    

    W powyższym przykładnie dostaniemy kolekcję stringów zawierającą imię i nazwisko wszystkich administratorów

LINQ i wyrażenie lambda

Pisanie zapytań w powyższy sposób może być nie do końca wygodne, dlatego z pomocą przychodzą nam wyrażenia lambda, które opisałem wcześniej. Napiszmy sobie zatem kilka przykładów wykorzystujących wyrażenia lambda.

            // Wybranie pracowników na stanowisku administratora
            IEnumerable<Employee> selectedEmplyees = Employees.Where(oEmployee => oEmployee.Position == Position.Administrator);

            // Wybranie pracowników na stanowisku administratora i developera
            IEnumerable<Employee> selectedEmplyees2 = Employees
                .Where(oEmployee => oEmployee.Position == Position.Administrator && oEmployee.Position == Position.Developer);

            // Wybranie jedynie imienia i nazwiska pracowników na stanowisku administratora
            IEnumerable<string> selectedEmplyeesNames = Employees
                .Where(oEmployee => oEmployee.Position == Position.Administrator)
                .Select(oEmployee => oEmployee.LastName + " " + oEmployee.FirstName);

            // Wybranie pracownika o najwiekszych zarobkach
            IEnumerable<Employee> selectedMostEarning = Employees.OrderByDescending(oEmployee => oEmployee.Reward).Take(1);

            // Suma wypłat dla wszystkich pracowników
            double sumOfPayout = Employees.Sum(oEmployee => oEmployee.Reward);

            // Ilosć pracowników ze stażem pracy większym, bądź równym 10 lat
            int countEmployees = Employees.Count(oEmployee => oEmployee.EmploymentDate >= DateTime.Now.AddYears(-10));

Jak możecie się domyślić w ten sposób to również działa.

W kolejnym wpisie postaram się bardziej szczegółowo zająć operatorami używanymi w powyższych przykładach.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *