Wprowadzenie

Artykuł ten będzie poświęcony Entity Framework z perspektywy osoby początkującej a przeznaczony przede wszystkim dla programistów używających na co dzień ADO.NET jako warstwy dostępu do danych. Dla doświadocznych programistów ten artykuł będzie bardzo prosty, gdyż wiedza tutaj przedstawiona została przygotowana specjalnie dla osób początkujących.

ADO.NET jest bardzo dobrym narzędziem dostępu do danych. Istnieje od wielu lat i wykorzystywanych jest w wielu systemach. Programista, który nie miał nic wspólnego z ORM zapewne będzie chciał wiedzieć czym jest Entity Framework? Jako są korzyści z jego używania i czy jest alternatywą dla ADO.NET?

Odpowiedź na pierwsze pytanie: Entity Framework jest narzędziem mapowania obiektowo-relacyjnego, tj. ORM. Generuje obiekty biznesowe oraz encje zgodnie z tabelami baz danych. Obiekt biznesowy jest encją (entity) w wielowarstowej aplikacji, która działa w połączeniu z dostępem do bazy danych oraz wartwą logiki biznesowej służącą do przesyłania danych. Z kolei nasze entity odnosi się do czegoś co jest unikatowe i istnieje oddzielnie, np. tabela bazy danych. Entity Framework pozwala na:

  1. Wykonywanie podstawowych operacji CRUD (Create, Read, Update, Delete).
  2. Łatwe zarządzanie relacjami 1 do 1, 1 do wielu oraz wiele do wielu.
  3. Tworzenie relacji dziedziczenia pomiędzy encjami.

Odpowiedzią na drugie pytanie są zalety używania tego rozwiązania:

  1. Cała logika dostępu do bazy danych może być napisana w języku wyższego poziomu.
  2. Koncepcyjny model może zostać zaprezentowany w lepszy sposób za pomocą relacji pomiędzy encjami.
  3. Bazowe dane mogą być zastąpione bez większego narzutu ponieważ cała logika biznesowa dostępu do danych jest na wyższym poziomie.

Odpowiedź na ostatnie pytanie nie jest jednoznaczna. Czy jest to alternatywa dla ADO.NET? I takie i nie...
Tak, ponieważ programista nie będzie musiał pisał metod i klas do wykonywania operacji. Nie, ponieważ model ten jest napisany w rzeczywistości w ADO.NET, tzn. przy użyciu tej technologii, więc wciąż używamy ADO.NET. Spójrzcie na poniższy diagram:

Architektura Entity Framework


Konfiguracja

Postaramy się zrozumieć użycie Entity Framework dzięki wykonywaniu prostych operacji CRUD. Jak tylko spojrzycie na kod i zobaczycie z jaką łatwością możecie wykonywać te operacjie zrozumieć dlaczego warto używać tej technologii.

Baza danych
W artykule będę używał tradycyjnej już bazy danych AdventureWorks udostępnionej przez Microsoft. Możecie ją pobrać z poniższego linka -> http://msftdbprodsamples.codeplex.com/

Model
Po pobraniu bazy danych możemy przystąpić do przygotowania modelu. Do naszego projektu dodajemy ADO.NET Entity Data Model:

Tworzenie nowego modelu Entity Framework

Następnie musimy wskazać sposób, którym chcemy się posłużyć tworząc nasz model:

Entity Framework - dostęp do danych na podstawie istniejącej bazy

Jak widzicie mamy do wyboru kilka możliwych podejść. W przypadku tego artykułu skupimy się na pierwszym podejściu, utworzymy nasz model na podstawie istniejącej bazy danych. Jak tylko model będzie gotowy utworzą się encje dla każdej z tabel. Encja dla tabeli Employee wygląda w następujący sposób:

Entity Framework - tabela Employee

Ponadto, zostały również utworzone klasy, które pozwolą nam na wykonywanie operacji na bazie danych. Pozostało nam tylko dowiedzieć się, w jaki sposób należy wykonywać operacje na bazie danych.

Entity Framework - utworzona klasa na podstawie tabeli Employee


Użycie kodu

Wszystko jest już gotowe do testowania. Wspomniana przeze mnie nieco wyżej automatycznie utworzona klasa pozwalająca na wykonywanie operacji na bazie danych to AdventureWorks2012_DataEntities. Przejdźmy do właściwych przykładów.

Pobieranie danych

AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
var employee = db.Employee;
// Wyświetlamy przykładowe dane w konsoli
foreach (var item in employee)
{
    // Poniższy zapis to nowość w języku C# w wersji 6.0
    // Poprzedni sposób formatowania tekstu
    // Console.WriteLine("Id: {0}, Płeć: {1}, Data zatrudnienia: {2}", item.BusinessEntityID, item.Gender, item.HireDate)
    Console.WriteLine($"Id: {item.BusinessEntityID}, Płeć: {item.Gender}, Data zatrudnienia: {item.HireDate}");
}
Gdybyśmy chcieli wyświetlić pracowników zatrudnionych w roku 2007 wystarczy niewielka modyfikacja:
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
var employeeHiredIn2007 = db.Employee.Where(a => a.HireDate.Year == 2007);
// Wyświetlamy przykładowe dane w konsoli
foreach (var item in employeeHiredIn2007)
{
    Console.WriteLine($"Id: {item.BusinessEntityID}, Płeć: {item.Gender}, Data zatrudnienia: {item.HireDate}");
}
A kolejną modyfikacją może być zwrócenie dokładnie jednego pracownika:
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
Employee spec = db.Employee.Single(a => a.BusinessEntityID == id);
Console.WriteLine("Wskazany pracownik: ");
Console.WriteLine($"Id: {spec.BusinessEntityID}, Płeć: {spec.Gender}, Data zatrudnienia: {spec.HireDate}");
Dodawanie danych
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
// Definiujemy nowy obiekt do dodania
Department dp = new Department();
dp.GroupName = "Sample Group Name";
dp.ModifiedDate = DateTime.Now;
dp.Name = "Sample Name";
// Dodajemy obiekt do naszej tabeli
db.Department.Add(dp);
// Zapisujemy zmiany
db.SaveChanges();
Aktualizowanie danych
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
// Do naszej metody przekazaliśmy id ostatniego elememtu
Department dp = db.Department.Single(a => a.DepartmentID == idToUpdate);
dp.Name = "Zaktualizowana nazwa departamentu";
// Wystarczy zapisać zmiany
db.SaveChanges();
Console.ReadKey();
Kasowanie danych
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
// pobieramy element, który chcemy usunąć
Department dep = db.Department.Single(a => a.DepartmentID == getLastId);
// kasujemy wpis z tabeli
db.Department.Remove(dep);
// zapisujemy zmiany
db.SaveChanges();
Wywołanie procedury składowanej
AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
foreach (var item in db.uspGetEmployeeManagers(3))
{
    Console.WriteLine($"Id: {item.BusinessEntityID}, Imie: {item.FirstName}, Nazwisko: {item.LastName}");
}
Komplenty kod
using System;
using System.Linq;
namespace EntityFrameworkWprowadzenie
{
    class Program
    {
        static void Main(string[] args)
        {
            NextAction();
        }
        public static void NextAction()
        {
            Console.WriteLine("Wybierz działanie, które chesz wykonać na bazie danych: ");
            Console.WriteLine("1: Pobranie danych z tabeli Employee");
            Console.WriteLine("2: Pobranie zatudnionych w roku 2007 z tabeli Employee");
            Console.WriteLine("3: Pobranie wybranego pracownika");
            Console.WriteLine("4: Dodanie nowego Departamentu");
            Console.WriteLine("5: Aktualizacja nowo dodanego Departamentu");
            Console.WriteLine("6: Pobranie danych z tabeli Department");
            Console.WriteLine("7: Usunięcie ostatniego rekordu z tabeli Department");
            Console.WriteLine("8: Opuścić aplikację: ");
            Console.Write("Twój wybór: ");
            int actionId = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine();
            switch (actionId)
            {
                case 1:
                    GetAllData();
                    break;
                case 2:
                    HiredIn2007();
                    break;
                case 3:
                    SpecificEmployeeId(32);
                    break;
                case 4:
                    AddNewDepartment();
                    break;
                case 5:
                    int getLastId = GetLastAddedRow();
                    UpdateData(getLastId);
                    break;
                case 6:
                    GetAllDataFromDepartment();
                    break;
                case 7:
                    getLastId = GetLastAddedRow();
                    DeleteSpecifiedId(getLastId);
                    break;
                case 8:
                    System.Environment.Exit(0);
                    break;
                default:
                    break;
            }
            Console.ReadKey();
        }
        private static void DeleteSpecifiedId(int getLastId)
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            // pobieramy element, który chcemy usunać
            Department dep = db.Department.Single(a => a.DepartmentID == getLastId);
            // kasujemy wpis z tabeli
            db.Department.Remove(dep);
            // zapisujemy zmiany
            db.SaveChanges();
            Console.WriteLine();
            NextAction();
        }
        private static void GetAllDataFromDepartment()
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            var dep = db.Department;
            foreach (var item in dep)
            {
                Console.WriteLine($"Id: {item.DepartmentID}, Nazwa: {item.Name}, Nazwa grupy: {item.GroupName}");
            }
            Console.WriteLine();
            NextAction();
        }
        private static void UpdateData(int idToUpdate)
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            // Do naszej metody przekazaliśmy id ostatniego elememtu
            Department dp = db.Department.Single(a => a.DepartmentID == idToUpdate);
            dp.Name = "Zaktualizowana nazwa departamentu";
            // Wystarczy zapisać zmiany
            db.SaveChanges();
            Console.WriteLine();
            NextAction();
        }
        private static int GetLastAddedRow()
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            var dep = db.Department.OrderByDescending(a => a.DepartmentID).FirstOrDefault();
            // Console.WriteLine("Ostatni wiersz w tabeli Department: ");
            // Console.WriteLine($"Id: {dep.DepartmentID}, Nazwa: {dep.Name}, Nazwa grupy: {dep.GroupName}");
            return dep.DepartmentID;
        }
        private static void AddNewDepartment()
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            // Definiujemy nowy obiekt do dodania
            Department dp = new Department();
            dp.GroupName = "Sample Group Name";
            dp.ModifiedDate = DateTime.Now;
            dp.Name = "Sample Name";
            // Dodajemy obiekt do naszej tabeli
            db.Department.Add(dp);
            // Zapisujemy zmiany
            db.SaveChanges();
            Console.WriteLine();
            NextAction();
        }
        static void GetAllData()
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            var employee = db.Employee;
            // Wyświetlamy przykładowe dane w konsoli
            foreach (var item in employee)
            {
                Console.WriteLine($"Id: {item.BusinessEntityID}, Płeć: {item.Gender}, Data zatrudnienia: {item.HireDate}");
            }
            Console.WriteLine();
            NextAction();
        }
        static void HiredIn2007()
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            var employeeHiredIn2007 = db.Employee.Where(a => a.HireDate.Year == 2007);
            // Wyświetlamy przykładowe dane w konsoli
            foreach (var item in employeeHiredIn2007)
            {
                Console.WriteLine($"Id: {item.BusinessEntityID}, Płeć: {item.Gender}, Data zatrudnienia: {item.HireDate}");
            }
            Console.WriteLine();
            NextAction();
        }
        static void SpecificEmployeeId(int id)
        {
            AdventureWorks2012_DataEntities db = new AdventureWorks2012_DataEntities();
            Employee spec = db.Employee.Single(a => a.BusinessEntityID == id);
            Console.WriteLine("Wskazany pracownik: ");
            Console.WriteLine($"Id: {spec.BusinessEntityID}, Płeć: {spec.Gender}, Data zatrudnienia: {spec.HireDate}");
            Console.WriteLine();
            NextAction();
        }
    }
}
Adnotacja dotycząca relacji i Lazy Loading

Aby w pełni zrozumieć Entity Framework należy zrozumieć kilka rzeczy takich jak: nazewnictwo, relacje między tabelami, relacje między encjami. Ponadto koncepcja Lazy Loading pozwoli na dużo bardziej efektywne używanie tej technologii.

Entity Framework jest używany juz od dłuższego czasu. Wielu programistów zaczyna dopiero swoją przygodę z tą technologią i to dla nich został napisany ten artykuł. Nie powinien być również traktowany jako kompletny poradnik. Ponadto, kod jest napisany w bardzo prosty sposób i jest wiele miejsc, które należałoby zoptymalizować. Ponieważ jednak chodziło o zrozumienie podstaw technologii dlatego kod został napisany w taki a nie inny sposób.