Pokazywanie postów oznaczonych etykietą sql. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą sql. Pokaż wszystkie posty

niedziela, 6 października 2013

[SQL] T-SQL: Common Table Expression

CTE występuje tylko w Transact - SQL-u i można go nazwać tymczasowym wynikiem otrzymanym w wyniku jednego zapytania na potrzebny innej operacji. Rozwiązanie takie poprawia czytelność dużych zapytań i może być wykorzystane w różnych sytuacjach:
  • zapytania rekurencyjne
  • odwoływanie się do jakiegoś wyniku wielokrotnie (dzięki nazwanemu CTE odwołujemy się do nazwy)
  • grupowanie wynikające z pewnych operacji agregujących
Poniżej przykład użycia Common Table Expression.

USE Cars;
GO
WITH Rated_Cars_CTE (Make, Score, Price)
AS
( SELECT Marka, ISNULL([Ocena subiektywna],0) + 
ISNULL([Ocena subiektywna],0), [Cena 2008]
FROM dbo.Cars
WHERE [Cena 2008] IS NOT NULL)

SELECT t.RANGE AS [Score Range], count(*) AS [Number of Occurences]
FROM (
  SELECT CASE  
    WHEN score between 0 and 1 THEN ' 0- 1'
    WHEN score between 1 and 2 THEN ' 1- 2'
    WHEN score between 2 and 3 THEN ' 2- 3'
    WHEN score between 3 and 4 THEN ' 3- 4'
    WHEN score between 4 and 5 THEN ' 4- 5'
    WHEN score between 5 and 6 THEN ' 5- 6'
    WHEN score between 6 and 7 THEN ' 6- 7'
    WHEN score between 7 and 8 THEN ' 7- 8'
    WHEN score between 8 and 9 THEN ' 8- 9'
    WHEN score between 9 and 10 THEN ' 9- 10'
    ELSE '10+' END AS RANGE
  FROM Rated_Cars_CTE) t
GROUP BY t.RANGE

czwartek, 28 marca 2013

[HTML|JS|CSS] HTML5: WebSQL

WebSQL jest jedną z tych funkcjonalności, które pomimo iż kojarzone są ze standardem HTML5, nie znajdą się w tym standardzie. Mimo wszystko wiele przeglądarek wspiera możliwość składowania danych w relacyjnej bazie po stronie klienta. API dostępne z JavaScript pozwala na pisanie zapytań w języku SQL, a dane składowane są w pliku (podobnie jak w SQLite). Jeżeli chodzi o kompatybilność z nowoczesnymi przeglądarkami, to problem pojawia się w przypadku Firefoxa i Explorera, co wyklucza sporą część użytkowników (link).

Aby połączyć się z bazą wykorzystujemy funkcję openDatabase:

var db = openDatabase('mydb', '1.0', 'my first database', 2 * 1024 * 1024);

Jeżeli baza nie istnieje to zostaje automatycznie utworzona. Instrukcje SQL wykonywane są w funkcjach zapewniających tranzakcyjność (jeżeli którekolwiek z zapytań się nie powiedzie, cała operacja zostanie cofnięta).

$("#create").click(function(){
 db.transaction(function (tx) {
  tx.executeSql('CREATE TABLE messages (id unique, text)');
 });
});

Tranzakcje można zagnieżdżać, gdyż w callbackach odczytujemy wyniki zapytań.

$("#store").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length + 1;
   tx.executeSql('INSERT INTO messages (id, text) VALUES (?,?)',
    [i, value]);
  });
 });
 $("#msg").val("");
});

Dane, które chcemy wyświetlić, można filtrować zgodnie ze składnią SQL.

$("#restore2").click(function(){
 var items = $("#items");
 items.html("");
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages" WHERE LENGTH(text) > 5', 
   [], function(tx,results){
   var l = results.rows.length;
   for (var i = 0; i < l; i++) {
    var item = results.rows.item(i);
    var li = $("<li>");
    $(li).html(item.id + ", " + item.text);
    items.append(li);
   };
  });
 });
});

Usuwanie z tabeli wykonuje się oczywiście przy użyciu instrukcji DELETE.

$("#removeLast").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length;
   var sql = 'DELETE FROM "messages" WHERE "id" = ' + i;
   tx.executeSql(sql);
  });
 });
});

Na koniec warto pamiętać, że podobnie jak w przypadku IndexedDB, możemy łatwo oglądać stan naszych tabel w przeglądarce Google Chrome.

piątek, 25 stycznia 2013

Bazy danych

Wszystko na temat baz danych: zarówno relacyjnych, wraz z narzędziami ORM, jak i nowego trendu NoSQL. Spis postów na temat ORM i SQL

niedziela, 30 grudnia 2012

[SQL|ORM] Entity Framework : Stored Procedures

Procedury składowane to narzędzie, z którego bardzo często warto skorzystać. Przeniesienie niektórych obliczeń na serwer bazy danych może usprawnić działanie aplikacji i uporządkować ją z logicznego punktu widzenia. Ponieważ procedury "żyją" po stronie bazy danych, należy wiedzieć, jak wywoływać je z poziomu narzędzia ORM. Poniżej kilka praktycznych przykładów w EF:

Procedura zwracająca kolekcję encji

Dla tabeli Customer mamy przykładową procedurę składowaną:

CREATE PROCEDURE dbo.GetCustomers
(@Company VARCHAR(50), @ContactTitle VARCHAR(50))
AS
 BEGIN
  SELECT * FROM Customer
  WHERE (@Company IS null OR Company = @Company) AND
  (@ContactTitle is null or ContactTitle = @ContactTitle)
 END

Po dodaniu do EDM odpowiednich encji i procedury GetCustomers, przechodzimy do okna designera. Z menu dostępnego pod prawym przyciskiem myszy wybieramy opcję Add -> Function Import...
W menu należy podać nazwę procedury, oraz określić co ma zwracać ( w tym przypadku kolekcję encji typu Customer). To wszystko, teraz w kodzie możemy na obiekcie kontekstu wołać GetCustomers.

var allCustomers = context.GetCustomers("GoShopNow.com", "Sales Manager");
foreach (var customer in allCustomers)
{
    Console.WriteLine("\t{0}", customer.Name);
}

Procedura zwracająca wartości przez parametr

Przykładowa procedura przyjmująca jeden parametr wejściowy i dwa wyjściowe, zwracająca dodatkowo kolekcję encji.

CREATE PROCEDURE dbo.GetVehiclesWithRentals
(@date DATE,
@totalRentals INT OUTPUT,
@totalPayments DECIMAL(18,2) OUTPUT)
AS
 BEGIN
 SELECT @totalRentals = COUNT(*), @totalPayments = SUM(Payment)
 FROM dbo.Rental
 WHERE RentalDate = @date
 
 SELECT DISTINCT v.*
 FROM dbo.Vehicle AS v JOIN dbo.Rental AS r
 ON v.VehicleId = r.VehicleId 
END

Procedurę taką dodajemy w sposób analogiczny, jak poprzednio. Różnice pojawiają się w kodzie. Korzystamy z obiektów typu ObjectParameter

string reportDate = "2/2/2010";
var totalRentals = new ObjectParameter("totalRentals", typeof (int));
var totalPayments = new ObjectParameter("totalPayments", typeof(decimal));
var vehicles = context.GetVehiclesWIthRentals(DateTime.Parse(reportDate), 
    totalRentals, totalPayments);
foreach (var vehicle in vehicles)
{
    Console.WriteLine("{0} {1}, {2} year", vehicle.Manufacturer, vehicle.Model,
        vehicle.Year);
}
Console.WriteLine("Total Rentals: {0}", totalRentals.Value);
Console.WriteLine("Total Payments: {0}", totalPayments.Value);

Custom Functions

Custom Functions to funkcje definiowane na poziome storage model. Nie musimy zatem (być może nie mamy dostępu) tworzyć procedur na serwerze bazy danych, a możemy je dodać do SSDL. Zatem treść procedury dodajemy do schematu bazy <Schema></Schema> edytując plik.edmx jako XML. Na przykład można dodać taką procedurę:

<Function Name="MembersWithTheMostMessages" IsComposable="false">
  <CommandText>
    select m.*
    from dbo.Member m
    join
    (
    select distinct msg.MemberId
    from dbo.Message msg where DateSent = @datesent
    ) temp on m.MemberId = temp.MemberId
  </CommandText>
  <Parameter Name="datesent" Type="date" />
</Function>

Funkcję taką wykorzystuje się tam samo jak procedurę składowaną

var members = context.MemberWithTheMostMessages(today);

sobota, 22 grudnia 2012

[SQL|ORM] Entity Framework : CSDL, MSL, SSDL

Pliki .edmx opisane językiem XML składają się z kilku części. Wszystkie zmiany wykonywane w Entity Designerze zapisywane są właśnie w plikach .edmx. Dane z CSDL, MSL i SSDL możemy zaszyć w assembly, bądź wyeksportować do wyjściowego katalogu zaznaczając opcję Metada Artifact Processing na Copy To Output Directory.

CSDL (Conceptual Schema Definition Language) - język opisujący encje relacje i funkcje, czyli schemat bazy danych na poziomie konceptualnym. Typy danych odpowiadają typom z CLR.

Przykładowy plik .csdl

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="EntityFrameworkRecipesModel" Alias="Self"
 xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" 
 xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
  <EntityContainer Name="EntityFrameworkRecipesEntities1"
   annotation:LazyLoadingEnabled="true">
    <EntitySet Name="Poem" EntityType="EntityFrameworkRecipesModel.Poem" />
    <EntitySet Name="Poet" EntityType="EntityFrameworkRecipesModel.Poet" />
    <AssociationSet Name="FK_Poem_Poet" Association="EntityFrameworkRecipesModel.FK_Poem_Poet">
      <End Role="Poet" EntitySet="Poet" />
      <End Role="Poem" EntitySet="Poem" />
    </AssociationSet>
  </EntityContainer>
  <EntityType Name="Poem">
    <Key>
      <PropertyRef Name="PoemId" />
    </Key>
    <Property Name="PoemId" Type="Int32" Nullable="false" />
    <Property Name="PoetId" Type="Int32" />
    <Property Name="Title" Type="String" MaxLength="Max" Unicode="false" FixedLength="false" />
    <Property Name="MeterId" Type="Int32" />
    <NavigationProperty Name="Poet" Relationship="EntityFrameworkRecipesModel.FK_Poem_Poet" 
      FromRole="Poem" ToRole="Poet" />
  </EntityType>
  <EntityType Name="Poet">
    <Key>
      <PropertyRef Name="PoetId" />
    </Key>
    <Property Name="PoetId" Type="Int32" Nullable="false" />
    <Property Name="FirstName" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="MiddleName" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="LastName" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <NavigationProperty Name="Poem" Relationship="EntityFrameworkRecipesModel.FK_Poem_Poet" 
      FromRole="Poet" ToRole="Poem" />
  </EntityType>
  <Association Name="FK_Poem_Poet">
    <End Role="Poet" Type="EntityFrameworkRecipesModel.Poet" Multiplicity="0..1" />
    <End Role="Poem" Type="EntityFrameworkRecipesModel.Poem" Multiplicity="*" />
    <ReferentialConstraint>
      <Principal Role="Poet">
        <PropertyRef Name="PoetId" />
      </Principal>
      <Dependent Role="Poem">
        <PropertyRef Name="PoetId" />
      </Dependent>
    </ReferentialConstraint>
  </Association>
</Schema>

SSDL (Store Schema Definition Language) - język opisujący schemat bazy danych na poziomie zasobu, w którym dane są przechowywane (bazy danych). Typy propercji są typami z SQL.

Przykładowy plik .ssdl

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="EntityFrameworkRecipesModel.Store" Alias="Self" 
  Provider="System.Data.SqlClient" ProviderManifestToken="2008" 
  xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" 
  xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
  <EntityContainer Name="EntityFrameworkRecipesModelStoreContainer">
    <EntitySet Name="Poem" EntityType="EntityFrameworkRecipesModel.Store.Poem" 
      store:Type="Tables" Schema="dbo" />
    <EntitySet Name="Poet" EntityType="EntityFrameworkRecipesModel.Store.Poet" 
      store:Type="Tables" Schema="dbo" />
    <AssociationSet Name="FK_Poem_Poet" Association="EntityFrameworkRecipesModel.Store.FK_Poem_Poet">
      <End Role="Poet" EntitySet="Poet" />
      <End Role="Poem" EntitySet="Poem" />
    </AssociationSet>
  </EntityContainer>
  <EntityType Name="Poem">
    <Key>
      <PropertyRef Name="PoemId" />
    </Key>
    <Property Name="PoemId" Type="int" Nullable="false" />
    <Property Name="PoetId" Type="int" />
    <Property Name="Title" Type="varchar(max)" />
    <Property Name="MeterId" Type="int" />
  </EntityType>
  <EntityType Name="Poet">
    <Key>
      <PropertyRef Name="PoetId" />
    </Key>
    <Property Name="PoetId" Type="int" Nullable="false" />
    <Property Name="FirstName" Type="varchar" MaxLength="50" />
    <Property Name="MiddleName" Type="varchar" MaxLength="50" />
    <Property Name="LastName" Type="varchar" MaxLength="50" />
  </EntityType>
  <Association Name="FK_Poem_Poet">
    <End Role="Poet" Type="EntityFrameworkRecipesModel.Store.Poet" Multiplicity="0..1" />
    <End Role="Poem" Type="EntityFrameworkRecipesModel.Store.Poem" Multiplicity="*" />
    <ReferentialConstraint>
      <Principal Role="Poet">
        <PropertyRef Name="PoetId" />
      </Principal>
      <Dependent Role="Poem">
        <PropertyRef Name="PoetId" />
      </Dependent>
    </ReferentialConstraint>
  </Association>
</Schema>

MSL(Mapping Specification Language) - język mapowania pomiędzy typami z SSDL i CSDL

Przykładowy plik .msl

<?xml version="1.0" encoding="utf-8"?>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
  <EntityContainerMapping StorageEntityContainer="EntityFrameworkRecipesModelStoreContainer" 
    CdmEntityContainer="EntityFrameworkRecipesEntities1">
    <EntitySetMapping Name="Poem">
      <EntityTypeMapping TypeName="EntityFrameworkRecipesModel.Poem">
        <MappingFragment StoreEntitySet="Poem">
          <ScalarProperty Name="PoemId" ColumnName="PoemId" />
          <ScalarProperty Name="PoetId" ColumnName="PoetId" />
          <ScalarProperty Name="Title" ColumnName="Title" />
          <ScalarProperty Name="MeterId" ColumnName="MeterId" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>
    <EntitySetMapping Name="Poet">
      <EntityTypeMapping TypeName="EntityFrameworkRecipesModel.Poet">
        <MappingFragment StoreEntitySet="Poet">
          <ScalarProperty Name="PoetId" ColumnName="PoetId" />
          <ScalarProperty Name="FirstName" ColumnName="FirstName" />
          <ScalarProperty Name="MiddleName" ColumnName="MiddleName" />
          <ScalarProperty Name="LastName" ColumnName="LastName" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>
  </EntityContainerMapping>
</Mapping>

Cała struktura CSDL, MSL i SSDL jest generowana przez Entity Designer, jednak nic nie stoi na przeszkodzie, by ręczenie edytyować fragmenty pliku .edmx do naszych potrzeb.

czwartek, 6 grudnia 2012

[SQL|ORM] Entity Framework : Queries

Przegląd zagadnień związanych z pisaniem zapytań w Entity Framework:

Wyrażenia SQL

Do wykonywania zapytań SQL korzystać można z metody ExecuteStoreCommand wołanej na obiekcie kontekstu. Metodzie przekazujemy przez string treść zapytania, oraz przez tablicę zestaw parametrów zapytania, zwraca ona ilość zmienionych rekordów w bazie.

using (var context = new EntityFrameworkRecipesEntities())
{
    string sql =
        @"insert into dbo.Payment(Amount,Vendor)
                   values (@Amount, @Vendor)";
    var args = new DbParameter[]
                   {
                       new SqlParameter() {ParameterName = "Amount", Value = 99.9M},
                       new SqlParameter() {ParameterName = "Vendor", Value = "Ace Plumbing"}
                   };
    int rowCount = context.ExecuteStoreCommand(sql, args);
}

Jeżeli chcemy zwrócić kolekcję obiektów, korzystamy z metody ExecuteStoreQuery<>.

using (var context = new EntityFrameworkRecipesEntities1())
{
    var sql = "select * from dbo.Student where Degree = @Major";
    var args = new DbParameter[]
                   {
                       new SqlParameter() {ParameterName = "Major", Value = "Masters"}
                   };
    var students = context.ExecuteStoreQuery<Student>(sql, args);
}

Wyrażenia EntitySQL

Entity SQL jest modyfikacją znanego SQL na potrzeby Entity Framework. Do tego typu zapytań wykorzystuje się metodę CreateQuery.

using (var context = new EntityFrameworkRecipesEntities2())
{
    var esql = "select value c from Customers as c";
    var customers = context.CreateQuery<Customer>(esql);
    foreach (var customer in customers)
    {
        Console.WriteLine("{0} {1}", customer.Name, customer.Email);
    }
}

Kluczowe jest słowo value, które umożliwia mapowanie rezultatu bezpośrednio do typu Customer.

Entity SQL może się okazać także przydatny w momencie, gdy modelujemy encje na zasadzie dziedziczenia typu "Tabela per Typ" i chcemy otrzymać jedynie encje pewnego typu. Przykładowo dla hierarchii encji jak poniżej


zapytanie wygląda następująco:

using (var context = new EntityFrameworkRecipesEntities3())
{
    var esql = "select value p from OfType(People,Querying.Teacher) as p";
    var teachers = context.CreateQuery<Teacher>(esql);
    Console.WriteLine("Teachers...");
    foreach (var teacher in teachers)
    {
        Console.WriteLine("{0}, isProfessor:{1}", teacher.Name, teacher.IsProfessor);
    }
}

Jako drugi parametr podajemy typ z CLR, który ma posłużyć jako filtr. Zatem w tym przykadku Querying jest nazwą namespace. Alternatywna wersja to:

var esql = "using Querying; select value p from OfType(People,Teacher) as p";

Zwracanie więcej niż jednej kolekcji danych przez procedurę składowaną

Mamy w bazie danych dwie tabele powiązane kluczem obcym, oraz procedurę składowaną, wybierającą wszystkie rekordy z obu tabel. Po dodaniu tabel i procedury do EDM, możemy skorzystać z tej procedury do pobrania dwóch kolekcji danych przy jednym wywołaniu. Niestety mapowanie takie jest nieco bardziej skomplikowane, niż przy wywoływaniu prostych zapytań SQL. Poniżej przykład procedury zwracającej dwa zestawy danych.

CREATE PROCEDURE [dbo].[GetBidDetails] 
AS
 BEGIN  
  SELECT * FROM Job
  SELECT * FROM Bid
 END

oraz sposób obsługi takiej procedury z poziomu Entity Framework:

using (var context = new EntityFrameworkRecipesEntities4())
{
    var cs = @"data source=PC-MKL;initial catalog=EntityFrameworkRecipes;
                integrated security=True;multipleactiveresultsets=True;
                App=EntityFramework";
    var conn = new SqlConnection(cs);
    var cmd = conn.CreateCommand();
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "dbo.GetBidDetails";
    conn.Open();
    var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
    var jobs = context.Translate<Job>(reader, "Jobs", MergeOption.AppendOnly).ToList();
    reader.NextResult();
    context.Translate<Bid>(reader, "Bids", MergeOption.AppendOnly).ToList();
    foreach (var job in jobs)
    {
        Console.WriteLine("Job: {0}", job.JobDetails);
        foreach (var bid in job.Bids)
        {
            Console.WriteLine("\tBid: {0} from {1}", bid.Amount, bid.Bidder);
        }
    }
}

Aby obsłużyć wiele kolekcji jako wynik procedury musimy skorzystać z obiektu klasy SqlCommand zwracanej przez SqlConnection. Mapowanie kolekcji zwróconej przez procedurę następuje przy pomocy metody Translate, której podajemy, na który obiekt z kontekstu ma zostać zmapowany wynik. Opcja AppendOnly zapewnia śledzenie zmian wykonanych na zwróconych obiektach. Polecenie ToList() wymusza natychmiastowe wykonanie zapytania. Dzięki metodzie NextResult przechodzimy do następnego zbioru wynikowego.

Porównania do kolekcji w pamięci

Pisząc zapytania często potrzebujemy filtrować wyniki po tym, czy wartości występują w pewnej kolekcji danych. W takiej sytuacji nie możemy bezpośrednio skorzystać z operacji join w LINQ, natomiast możemy wykorzystać funkcję Contains. Zapytanie takie przetransformuje się na SQL-owy INNER JOIN.

using (var context = new EntityFrameworkRecipesEntities5())
{
    var cats = new List<string>() {"Programming", "Databases"};
    var books = from b in context.Books
                where cats.Contains(b.Category.Name)
                select b;
    foreach (var book in books)
    {
        Console.WriteLine("{0} : {1}", book.Category.Name, book.Title);
    }
}

Profiler pokazuje następujące zapytanie SQL: 

SELECT 
[Extent1].[BookId] AS [BookId], 
[Extent1].[Title] AS [Title], 
[Extent1].[CategoryId] AS [CategoryId]
FROM   [dbo].[Book] AS [Extent1]
INNER JOIN [dbo].[Category] AS [Extent2] ON [Extent1].[CategoryId] = [Extent2].[CategoryId]
LEFT OUTER JOIN [dbo].[Category] AS [Extent3] ON [Extent1].[CategoryId] = [Extent3].[CategoryId]
WHERE [Extent2].[Name] = N'Programming' OR [Extent3].[Name] = N'Databases'

Grupowanie po dacie

Mamy tabelę z kolumną typu DATE i chcemy pogrupować wyniki po dacie w Entity Framework. Korzystając z LINQ nie możemy grupować bezpośrednio po danym property, ponieważ typ DateTime z CLR nie przetłumaczy się bezpośrednio do SQL. Należy wykorzystać funkcję TruncateTime.

var groups = from r in context.Registrations
             group r by EntityFunctions.TruncateTime(r.RegistrationDate)
                into g
             select g;

Dane zostaną pogrupowane po dniach.

Grupowanie po wielu properties

Do grupowania po kilku properties można skorzystać z typów anonimowych.

var results = from e in context.Events
              group e by new {e.State, e.City}
              into g
              select new
                {
                    State = g.Key.State,
                    City = g.Key.City,
                    Events = g
                };

Join na wielu kolumnach

Przykładowo dla dwóch encji jak poniżej



możemy dokonać takiej operacji przy pomocy LINQ w następujący sposób.

var orders = from o in context.C_Order
             join a in context.C_Account on
                 new {Id = o.AccountId, City = o.ShipCity, State = o.ShipState}
                 equals
                 new {Id = a.AccountId, a.City, a.State}
             select o;

Korzystamy z faktu, że porównanie dwóch typów anonimowych jest porównaniem na zasadzie porównywania par properties i zwróci true jedynie, gdy wszystkie properties są sobie równe.

sobota, 1 grudnia 2012

[SQL|ORM] Entity Framework : Database First

Database First to podejście, w którym mamy już stworzoną bazę danych i chcemy ją jedynie zmapować na encje w naszej aplikacji. Przy takiej okazji pojawia się kilka ciekawych zagadnień.

Modelowanie relacji wiele do wielu

W relacyjnych bazach danych do tworzenia tego typu relacji stosuje się tzw. junction tables. Są to tabele, które zawierają przeważnie dwie kolumny - klucze obce do obu "stron" relacji. Prosty skrypt tworzący taką relację poniżej

CREATE TABLE Album (
  AlbumId INT IDENTITY PRIMARY KEY,
  AlbumName VARCHAR(50)
  )
 
CREATE TABLE Artist (
  ArtistId INT IDENTITY PRIMARY KEY,
  FirstName VARCHAR(50),
  MiddleName VARCHAR(50),
  SecondName VARCHAR(50)
  )
 
CREATE TABLE LinkTable (
  ArtistId INT NOT NULL ,
  AlbumId INT NOT NULL ,
  PRIMARY KEY (ArtistId, AlbumId) ,
  CONSTRAINT fk_Artist_has_Album
    FOREIGN KEY (ArtistId)
    REFERENCES Album (AlbumId),
  CONSTRAINT fk_Album_has_Artist
    FOREIGN KEY (AlbumId)
    REFERENCES Artist (ArtistId)
    );

Po dodaniu ADO .NET Entity Data Model i wybraniu powyższych tabel, otrzymamy w designerze dwie encje połączone relacją wiele do wielu.

Obie encje otrzymały dodatkowe Navigation Properties mapowane do LinkTable. Dzięki temu w łatwy sposób można dodawać Artystów do albumu (i na odwrót) przez dodanie referencji do kolekcji.

Nieco inaczej sytuacja wygląda w przypadku, gdy tabela łącząca zawiera dodatkowe dane.Wtedy Entity Framework utworzy trzy encje danych, jak w przykładzie poniżej:
Dane dodajemy zatem do tabel poprzez stworzenie trzech obiektów.

Modelowanie tabel zawierających referencje do samych siebie

Tabele zawierające klucz główny, oraz klucz obcy będący referencją do własnego klucza głównego są w relacji do samych siebie. Dodając taką tabelę do EDM dostaniemy encję z dwoma dodatkowymi Navigation Properties. Możemy zmienić ich nazwy, na takie, które będą odpowiadały ich reprezentacji. Jedna z nich będzie właściwością skalarną (relacja 0..1), drugą kolekcją (relacja 1..*). W ten sposób modelowane mogą być dane reprezentowane np. jako drzewa.

Encja taka może mieć jedną nadkategorię (lub nie mieć nadkategorii - relacja 0..1) oraz wiele podkategorii.

Jedna encja - wiele tabel

Mamy dwie tabele dzielące ten sam klucz główny. Z punktu widzenia programu możemy połączyć zatem dwie encje w jedną.



Po wybraniu obu tabel w okienku Entity Data Model, dostaniemy dwie encje w designerze. Aby je połączyć, należy:
  • Przekopiować właściwości z encji ProductWebInfo do Product
  • Usunąć ProductWebInfo, wybierając opcję No przy pytaniu o usuwanie tabel z store model
  • W widoku Mapping Details w VS dla encji Product należy dodać tabelę ProductWebInfo
  • Dla tabeli ProductWebInfo ustawić mapowania (SKU na property SKU, ImageURL na property ImageURL)
Podczas zapisywania obiektu encji Product do bazy danych zostaną dodane wpisy do obu tabel.

Jedna tabela - wiele encji

Sytuacja taka może być przydatna, gdy mamy w tabeli pewne pola dużych rozmiarów, z których rzadko korzystamy. Aby nie ładować ich nie potrzebnie za każdym razem możemy dodać tabelę do EDM, a następnie odpowiednio zmapować ją do dwóch encji:
  • Dodać nową encję do designera, zmieniając w polu klucza wartość na nazwę kolumny klucza głównego  z encji głównej
  • Przenieść rzadko używane właściwości do nowej encji
  • W Mapping Window na nowej encji dodać tabelę i ustawić mapowanie właściwości na odpowiednie kolumny
  • Dodać asocjację (PPM na jednej z encji) między dwoma encjami, ustawioną na 1 do 1, odznaczając dodawanie nowego klucza obcego
  • Ostatecznie klikając dwukrotnie na połączeniu należy dodać ograniczenia (Constraint) w Referential Constraint ustawiając Principal na Photograph, a właściwości klucza na PhotoId
Po poprawnej konfiguracji otrzymujemy Lazy Loading dla pól w dodatkowej encji. Warto pamiętać, że usuwając encję główną, usuwane także encję dodatkową, oraz, że aby dodać encję dodatkową do zbioru, musi istnieć odpowiadająca jej encja główna.

Modelowanie dziedziczenia "tabela per typ"

Dysponujemy jedną tabelą główną, oraz dwoma powiązanymi z nią relacjami kluczy obcych. Dwie dodatkowe tabele stanowią uzupełnienie informacji o obiekcie z tabeli głównej.



Z punktu widzenia aplikacji pisanej w języku obiektowym, dwie tabele dziedziczą z tabeli głównej. Aby zamodelować dziedziczenie w EF, należy po dodaniu trzech tabel do Entity Data Model:
  • usunąć wszystkie asocjacje
  • na encji odpowiadającej tabeli głównej wybrać Add...Inheritance. W dialog boxie wybrać tabelę bazową jako encję bazową oraz jedną z tabel uzupełniających, jako encję dziedziczącą
  • czynność powtórzyć dla drugiej tabeli
  • w oknie mapowania usunąć property odpowiadające kolumnie z kluczem obcym z encji dziedziczących i dodać je, jako mapowane do kolumny z tabeli głównej
W efekcie uzyskamy hierarchę obiektów jak poniżej:


Filtrowanie kolekcji obiektów

Podczas mapowania tabeli na encję możemy założyć filtr odrzucający pewne rekordy. Przykładowo możemy odrzucać wszystkie wpisy, gdzie pola z pewnej kolumny są różne od NULL. Po dodaniu tabeli do EDM, należy przejść do widoku Mapping Details, a następnie kliknąć w opcję <Add a Condition> . W dodanym warunku wybieramy kolumnę, rodzaj warunku (is lub =) i wartość (w tym przypadku is Null. Nie będziemy potrzebowali więcej property, które służy za warunek, zatem można je usunąć z listy właściwości na encji. Pozostałe możliwości warunków
  • <column> Not Null
  • <integer> = <value>
  • <String> = <value>

Modelowanie dziedziczenia "tabela per hierarchia"

Mamy tabelę, zawierającą jedną kolumnę, która determinuje typ (rodzaj) obiektu. Chcemy tak zmapować ją do encji, aby uzyskać jeden bazowy typ abstrakcyjny i dwa dziedziczące z niego. Po dodaniu naszej tabeli do Entity Data Model należy:
  • Dodać dwie dodatkowe encje w designerze, które będą reprezentowały typy dziedziczące. Podczas dodawania należy ustalić typ bazowy na encję mapowaną bezpośrednio do tabeli
  • Przenieść properties charakterystyczne dla danego typu z encji bazowych do odpowiednich encji dziedziczących
  • W widoku Mapping Details wybrać dla obu typów dziedziczących tabelę, do której będą mapowane, oraz sprawdzić poprawność mapowania properies nowej encji do odpowiednich kolumn tabeli
  • W tym samym widoku w polu Add a Condition należy dodać warunek na podstawie kolumny z tabeli głównej, np. 


W opcjach encji bazowej można ustawić pole abstract na true, oraz usunąć property decydujące o typie encji.



Modelowanie relacji typu "Is-a" oraz "Has-a"

Może się zdarzyć taka sytuacja że dwie tabele są w relacji przedstawionej poniżej:


Obie tabele posiadają ten sam klucz główny, a dodatkowo jedna posiada asocjację z drugą poprzez klucz obcy.

Aby uprościć dostęp do danych po stronie programu, możemy dodać je do EDM, a następnie:
  • usunąć asocjację 1...0.1
  • na tabeli bez klucza obcego dodać Inheritance i jako encję dziedziczącą dodać tą odpowiadającą drugiej tabeli
  • z encji dziedziczącej usunąć property odpowiadające kluczowi głównemu, a w szczegółach mapowania ustawić mapowanie tej kolumny (ParkId) na Id encji bazowej (LocationId)
  • zmienić nazwę Navigation Property reprezentującego klucz obcy
Uproszone encje wyglądają następująco:


Typy złożone

Jedna tabele reprezentuje kilka logicznych obiektów, np osobę i jej adres jednocześnie.

Po dodaniu do EDM tabeli, należy:
  • zaznaczyć properties z danej grupy i kliknąć opcję Refactor into New Complex Type dostępną pod prawym przyciskiem myszy
  • w widoku Model Browser możemy zmienić typ np. na Name oraz w Designerze możemy zmienić nazwę property
  • Complex property można także dodawać poprzez kliknięcie prawym przyciskiem myszy na encji i wybranie Add...ComplexProperty. 
  • W oknie szczegółów mapowania wszystkie properties powinny być mapowane na propercje typów złóżonych