Sunteți pe pagina 1din 16

Entity Framework 27.02.2012 Pag.

1/16

Entity Framework
Conexiuni, Tranzactii, Multithreading

Pentru o discutie completa a se consulta si Programming Entity Framework: Building Data


Centric Apps with the ADO.NET Entity Framework de Julia Lerman, publicat de O'Reilly
Media

Ce vom discuta:

• Cum controlam conexiunile?


• Exista connection pooling?
• Apelurile la baza de date sunt tranzactionale?

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 2/16

EntityConnection si conexiuni la baza de date in EF

EF foloseste stringul de conexiune din fisierul de configurare ce are extensia .config si va crea
si folosi automat o conexiune la baza de date.
In ADO.NET clasic trebuie sa realizam (in general) urmatoarele:
• sa deschidem o conexiune (DbConnection);
• sa instantiem si sa definim o comanda (DbCommand);
• sa executam comanda (ExecuteReader(), ExecuteScalar(), etc.);
• sa inchidem conexiunea.

In EF, ObjectContext face aceste lucruri in background. Exista situatii cand avem nevoie sa
controlam conexiunea in EF. Cum o facem?

EntityConnection versus DbConnection

EntityConnection nu e o conexiune la bd ci reprezinta o cale catre conexiunea la baza de


date.

EntityConnection consta din patru parti:

Metadata
Puncteaza la fisierele :
Conceptual Schema Definition Layer [CSDL];
Mapping Schema Layer [MSL];
Store Schema Definition Layer [SSDL].
Provider connection
Stringul de conexiune la baza de date.
Provider name
Namespace-ul furnizorului bazei de date.
Name
Numele stringului de conexiune.

EntityConnection poate fi definit in fisierul de configurare (.config).

In exemplul de mai jos sunt marcate cu rosu cele patru parti componente pentru
EntityConnection.

<connectionStrings>
<add
name="BreakAwayEntities"
connectionString=
"metadata=res://*/BAModel.csdl|res://*/BAModel.ssdl|
res://*/BAModel.msl;
provider=System.Data.SqlClient;
provider connection string='Data Source=myserver;
Initial Catalog=BreakAway;
Integrated Security=True;
MultipleActiveResultSets=True'"
providerName="System.Data.EntityClient"
/>
</connectionStrings>

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 3/16

Implicit, ObjectContext foloseste stringul de conexiune din fisierul de configurare care se


potriveste cu numele lui EntityContainer din interiorul modelului nostru.

Baza de date in etapa de dezvoltare versus Baza de date in productie

Cand distribuim in mediul real (productie) o baza de date unul din lucrurile care trebuiesc facute
este acela de modifica stringul de conectare din fisierul .config.
Stringul de conectare poate fi modificat si in mod programabil.

Clasa EntityConnection

EntityConnection se gaseste in namespace EntityClient.

In codul urmator, numele stringului de conexiune din fisierul .config este transferat ca un
parametru in ctor EntityConnection. Conexiunea in acest caz va fi creata din detaliile
furnizate in stringul de conexiune:

using (EntityConnecton conn = new EntityConnection("name=BAEntities");

Putem selecta un string de conexiune particular din fisierul .config cand instantiem un
ObjectContext astfel:

var context = new BAEntities("name=MyOtherConnectionString");

Metadata si parametrii furnizorului spatiului de nume (furnizor ADO.NET EF) nu sunt afisate ca
proprietati ale clasei EntityConnection, si acestia vor fi accesati in momentul executiei unei
cereri cand EntityClient are nevoie sa citeasca EDM (Entity Data Model) si atunci se
determina furnizorul bazei de date (ex. Data.Sql.SqlClient) caruia ii va fi transferata cererea
pentru a o procesa.

Daca dorim sa citim complet stringul de conexiune din fisierul de configurare, putem folosi:
metode din System.Configuration.ConfigurationManager
si clasa EntityConnectionStringBuilder.

Clasa EntityConnectionStringBuilder

Observatie
Aaugati referinta la System.Configuration pentru a putea folosi
ConfigurationManager.

EntityConnectionStringBuilder este derivat din DbConnectionStringBuilder.

Exemplu
1. Memoram locatia fisierelor cu metadata (.csdl, .msl, .ssdl) in fisierul de resurse.
2. Dorim ca EntityConnectionString sa puncteze la aceasta locatie.

Pentru 1. In Project->Properties->Settings cream proprietatea MetadataFilePath si


ii atribuim valoarea (calea spre fiserele .csdl, .msl, .ssdl):

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 4/16

C:\EFModels\Model.csdl|C:\EFModels\Model.ssdl|C:\EFModels\Model.msl

si in cod modificam EntityConnectionString.

// citesc string conexiune din fisierul de configurare


var connstring = ConfigurationManager
.ConnectionStrings["BAEntities"].ConnectionString;

// construiesc un obiect EntityConnectionStringBuilder


var estringnew = new EntityConnectionStringBuilder(connstring);

// modific proprietatea Metadata. Se cietste valoarea setata in Pas 1.


estringnew.Metadata = Properties.Settings.Default.MetadataFilePath;

// construiesc un nou context furnizand noul string de conexiune


var context = new BAEntities(estringnew.ToString());

// creez cererea cu noua conexiune


var query =
from con in context.Contacts
where con.Addresses.Any((a) => a.City == "Seattle")
select con;

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 5/16

Constructie EntityConnection

EntityConnection contine o referinta la modelul conceptual si o conexiune la sursa de date.


Un ctor pentru EntityConnection accepta ca parametru un string ce reprezinta stringul de
conexiune. Metoda EntityConnection.Open() va deschide o conexiune la sursa de date.

Un alt ctor pentru este EntityConnection(MetadataWorkspace, DbConnection). In


timpul construirii unui asemenea obiect, metadata este blocata si schimbari ale stringului de
conexiune nu mai sunt premise.

Metode si proprietati importante din aceasta clasa (lista completa in MSDN).

Name Description

BeginDbTransaction Starts a database transaction. (Inherited from


DbConnection.)
BeginTransaction() Begins a transaction by using the underlying
provider.
BeginTransaction(IsolationLevel) Begins a transaction with the specified isolation
level by using the underlying provider.
Close Closes the connection to the database.
CreateCommand Creates a new instance of an EntityCommand,
with the Connection set to this
EntityConnection.
CreateDbCommand Creates and returns a DbCommand object
associated with the current connection.
EnlistTransaction Enlists this EntityConnection in the specified
transaction.
Open Establishes a connection to the data source by
calling the underlying data provider's Open
method.

Name Description

ConnectionString Gets or sets the EntityConnection connection string.


ConnectionTimeout Gets the number of seconds to wait when attempting to establish a
connection before ending the attempt and generating an error.
Database Gets the name of the current database, or the database that will be
used after a connection is opened.
DataSource Gets the name or network address of the data source to connect to.
State Gets the ConnectionState property of the underlying provider if the

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 6/16

EntityConnection is open. Otherwise, returns Closed.


StoreConnection Provides access to the underlying data source connection that is used
by the EntityConnection object.

Lucrul cu conexiuni la baza de date

Metoda EntityConnection.Open() – apeleaza Connection.Open() dat de furnizorul de


ADO.NET.
EntityConnection.Close() – inchide conexiunea.

Cand un obiect ObjectContext executa o cerere, se creaza intern un obiect


EntityConnection si EntityCommand si apoi se executa comanda. In momentul cand s-a
terminat executia comenzii, conexiunea la baza de date se va inchide.

Deschidere / Inchidere conexiuni in mod programabil

Exista posibilitatea de a gestiona conexiunile in mod programabil (manual).

1. Deschidere conexiune in mod manual si inchidere automata cand conetxtul este


“disposed”.
2. Deschidere si inchidere conexiune in mod programabil.

Scenariu 1: Apeluri multiple pe o singura conexiune. « Cererea initiala si cele ce urmeaza a se


executa pe o singura conexiune» vezi exemplul de mai jos.

using (BAEntities context = new BAEntities())


{
// se creaza o conexiune implicita

// cererea ce foloseste aceasta conexiune


var cons =
from con in context.Contacts
where con.FirstName == "Jose"
select con;
foreach (var c in cons)
{
if (c.AddDate < new System.DateTime(2007, 1, 1))
{
// se foloseste aceeasi conexiune
c.Addresses.Load();
}
}
}
// s-a facut dispose pe context, deci conexiunea va fi inchisa

Observatie
Conexiunea nu e inchisa pana cand nu au fost “cititite” toate rezultatele. Metoda Load foloseste
aceeasi conexiune.

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 7/16

Scenariu 2: Conexiuni multiple – comportare implicita


Se executa doua cereri fiecare cu conexiune proprie.

using (BAEntities context = new BAEntities())


{
// definitie cerere
var cons = from con in context.Contacts
where con.FirstName == "Jose"
select con;

// executie cerere
var conList = cons.ToList();

// definitie cerere
var allCustomers = from con in context.Contacts.OfType<Customer>()
select con;

// executie cerere
var allcustList = allCustomers.ToList();
}

Conexiune explicita

Pentru a schimba comportarea implicita (cazul de mai sus) putem deschide conexiune la bd
programabil.

using (BAEntities context = new BAEntities())


{
// deschidere conexiune
context.Connection.Open();

// definitie cerere
var cons = from con in context.Contacts where
con.FirstName == "Jose"
select con;

// executie cerere
var conList = cons.ToList();

// definitie cerere
var allCustomers = from con in context.Contacts.OfType<Customer>()
select con;

// executie cerere
var allcustList = allCustomers.ToList();

// inchidere conexiune
context.Connection.Close();
}

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 8/16

Connection versus Connection.StoreConnection

Proprietatea StoreConnection din EntityConnection.


Permite de a seta anumite valori pentru o conexiune (time out, etc.) lucrand in mod direct cu
aceasta proprietate.

Cum sunt eliberate StoreConnection?

Conexiunea la o baza de date nu e o resursa “managed” (nu e eliminata de garbage collector) si


deci trebuie sa avem grija sa emitem cod pentru eliberare (“dispose”).

Eliberarea unui obiect ObjectContext are ca efect eliberarea conexiunii la baza de date.
ObjectContext poate fi eliberat in mod explicit sau preluat de garbage collector. In ultimul
caz conexiunea va fi eliberata in mod nedeterminist cu efecte colaterale asupra executiei codului
– in unele cazuri traducandu-se prin terminarea cu o exceptie a aplicatiei.

Codul pentru metoda ObjectContext.Dispose este dat mai jos :

protected virtual void Dispose(bool disposing)


{
if (disposing)
{
if (this._createdConnection & (this._connection != null))
{
this._connection.Dispose();
}
this._connection = null;
this._adapter = null;
}
}

Observatie
Cand folosim LINQ to Entities sau ObjectContext, conexiunea va fi eliberata.
Daca am creat conexiunea in mod explicit va trebui sa eliberam EntityConnection (dispose) sau
sa asteptam ca garbage collector sa elibereze resursa.

Ce se intampla cu connection pooling?

In termeni de resurse deschiderea unei conexiuni este o operatie costisitoare ca timp de aceea se
foloseste acest mecanism de retinere in memorie a obiectelor create si de reutilizare a acestora
atunci cand e nevoie.

Connection pooling este controlat de furnizorul de ADO.NET, deci va trebui sa citim cu atentie
documentatia pentru a vedea daca este implementat un asemenea mecanism.

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 9/16

Entity Framework si Tranzactii

Ce este o tranzactie?
 Commit.
 Rollback.

Cand executam o operatie SaveChanges (apel de metoda), EF implicit creaza o tranzactie.


Putem controla crearea tranzactiilor in mod programabil.

Trebuie avut in vedere si faptul ca putem executa operatii in cadrul unei tranzactii, operatii ce
lucreaza cu mai multe baze de date si/sau Message Queue (MSMQ). Daca una din aceste operatii
esueaza trebuie sa se faca rollback pe toate operatiile din toate bazele de date. Pentru a realiza
acest lucru exista o componenta in System.Transaction numita Windows Distributed
Transaction Coordinator (DTC) ce supervizeaza actiunile ce sunt mult mai complexe decat
DbTransaction.
System.Transaction poate decide daca e nevoie de DTC sau DbTransaction – adica o tranzactie
individuala.

Tranzactii implicite in EF

Scenariu
Consideram tabelele Contact si Address dintr-o baza de date. Logica este ca nu poate exista o
adresa fara a exista un Contact, si nu e folosit “delete” in cascada, adica daca stergem o
inregistrare din Contact sa se stearga automat toate inregistrarile din Address ce corespund acelui
Contact.

Cum se implementeaza acest lucru la nivel de baza de date?

Stergerea unei inregistrari din Contact pentru care exista inregistrari in Address va genera o
eroare.
Vom scrie o secventa de cod pentru a vedea o tranzactie in actiune.
O tranzactie implicita ce va executa roll back. In exemplul de mai jos se executa doua comenzi.

using (BAEntities context = new BAEntities())


{
var con = context.Contacts.Where(c => c.ContactID == 5)
.FirstOrDefault();

// sterg obiectul selectat


// din cauza unei constrangeri referentiale
// actiunea esueaza. Se forteaza roll back.
context.DeleteObject(con);

// cream o noua plata pentru rezervare


// nu vafi inregistrata in bd din cauza roll back de mai sus.
var res = context.Reservations.FirstOrDefault;
var newPayment = new Payment();
newPayment.Amount = "500";
newPayment.PaymentDate = System.DateTime.Now;
newPayment.Reservation = res;

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 10/16

// insereaza noua inregistrare in context si in baza de date


context.SaveChanges();
}

Dupa executia acestui cod pe partea de client apare o exceptie.

"The DELETE statement conflicted with the REFERENCE constraint


"FK_Address_Contact". The conflict occurred in database "BreakAway", table
"dbo.Address", column 'ContactID'. The statement has been terminated."

Observatie
Trebuiesc tratate exceptiile cand se executa SaveChanges.

Ce se intampla cu tranzactia?

Un DbTransaction este creat in interiorul metodei SaveChanges. daca nu apare nici o


exceptie in timpul executiei comenzii(lor) se apeleaza DbTransaction.Commit()si se
apeleaza in continuare ObjectContext.AcceptAllChanges() ce are ca efect realizarea unui
sync intre baza de date si entitatile definite si cu care se lucreaza in acel moment.
Daca nu se doreste acest “sync” atunci se apeleaza SaveChanges(false) si nu va mai fi
apelata metoda AcceptAllChanges.

ObjectContext.SaveChanges(false)

Daca suprascriem comportarea implicita, putem controla cand sa apelam AcceptAllChanges.

Specificarea unei tranzactii proprii

In momentul cand cream o tranzactie proprie, SaveChanges o va folosi pe aceasta si nu va crea


o alta tranzactie.

Crearea unei noi tranzactii inseamna folosirea unui obiect


System.Transaction.TransactionScope.
Putem folosi o tranzactie pentru citire sau scriere in bd, ceea ce inseamna ca va lucra cu
ObjectContext si EntityClient.

Observatie
Daca folosim LINQ to Entities si dorim sa folosim avantajele date de ObjectContext, trebuie sa
facem cast al cererii din LINQ to Entities la ObjectQuery.

In exemplul de mai jos folosim o tranzactie explicita.

Observatie
Trebuie adaugata referinta la System.Transactions.

using (var context = new BAEntities())


{
Customer cust = Customer.CreateCustomer
("George", "Jetson", "A real space cadet",new DateTime(1962,1,1));

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 11/16

context.AddToContacts(cust);
// In acest moment obiectul “cust” este in baza de date?

using (TransactionScope tran = new TransactionScope())


{
try
{
context.SaveChanges(false);
using (altDBEntities altcontext = new altDBEntities())
{
altcontext.AddToContact(Contact
.CreateContact(cust.LastName.Trim() + ", " + cust.FirstName));

altcontext.SaveChanges();
}

tran.Complete();
context.AcceptAllChanges();
}
catch
{
//throw or handle database of Entity Framework exceptions
throw;
}
}
}

Analiza codului. Cate tranzactii sunt? Analizati si codul de mai jos si reveniti asupra
raspunsului.

Un exemplu din MSDN in care se observa mai clar ce inseamna TransactionScope si DTC.

Se considera 2 stringuri de conexiune si doua comenzi.


Scenariul este ca a doua comanda ar trebui sa se execute numai daca prima comanda s-a executat
cu succes. In aceasta actiune sunt implicate doua baze de date (diferite) gestionate de aceeasi
instanta sau instante diferite ale serverului de baze de date.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{

int returnValue = 0;
System.IO.StringWriter writer = new System.IO.StringWriter();

try
{
// se creaza TransactionScope pt a executa comenzile,
// garantand astfel ca cele comenzi vor fi ambele Commit sau Rollback.

using (TransactionScope scope = new TransactionScope())


{
using (SqlConnection connection1 =
new SqlConnection(connectString1))
{

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 12/16

connection1.Open();

SqlCommand command1 = new SqlCommand(commandText1,


connection1);
returnValue = command1.ExecuteNonQuery();

// cod ...
// daca executia a ajuns aici inseamna ca
// command1 s-a executat cu succes.

using (SqlConnection connection2 =


new SqlConnection(connectString2))
{

// o noua tranzactie, intra in actiune DTC

connection2.Open();

// Executa comanda in a doua baza de date

returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2,
connection2);
returnValue = command2.ExecuteNonQuery();
// cod ..
}
}
}

// Metoda Complete va executa Commit pentru tranzactii.

scope.Complete();
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException
Message: {0}", ex.Message);
}
catch (ApplicationException ex)
{
writer.WriteLine("ApplicationException Message: {0}", ex.Message);
}

Console.WriteLine(writer.ToString());

return returnValue;
}

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 13/16

Cereri pentru citire folosind System.Transaction sau EntityTransaction

EntityTransaction este un wrapper pentru tranzactiile furnizorului bazei de date si apeleaza


EntityConnection.BeginTransaction pentru a o crea.

Exemplu cu tranzactie intr-o operatie de citire.

using (var econ = new EntityConnection("name=BAEntities"))


{
EntityTransaction eTran =
econ.BeginTransaction(IsolationLevel.ReadUncommitted);
econ.Open();
var eCmd = econ.CreateCommand();
eCmd.CommandText =
"SELECT con.contactID FROM BreakAwayEntities.Contacts AS con";
var dr = eCmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (dr.Read())
{
// cod
}
eTran.Commit();
}

De ce e nevoie sa folosim tranzactie in citire?

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 14/16

Putem folosi tranzactii in ObjectContext?

Modificarile facute in entitatile din context nu pot fi “roll back”.


O posibilitate este de a reimprospata datele apeland metoda ObjectContext.Refresh().
Vezi documentatia din MSDN.
O alta posibilitate este de a elibera contextul, de a crea unul nou si sa reimprospatam datele.
Aceste posibilitati nu inseamna « roll back ».

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 15/16

Multithreading

Observatie
(MSDN docs): "ObjectContext only supports Single-Threaded scenarios."

Un scenariu de folosire ObjectContext in cadrul unui fir


In cod sunt marcate zonele pentru blocare.
Analizati codul si sesizati ceea ce se executa in fiecare fir.

Albastru = creare si lansare fir ;


Rosu = folosire obiecte de blocare ;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BAGA;
internal static class Module2
{

// Delegate that defines the signature for the callback method.


//
public delegate void contextCallback(List<Contact> contactList);
private static List<Contact> contacts;

public class MainModule


{
public static void Main()
{
ObjectContextClass occ =
new ObjectContextClass(new contextCallback(ResultCallback));

Thread t = new Thread(occ.GetCustomers);


t.Start();
t.Join();

Console.WriteLine("Retrieved: " + contacts.Count.ToString());


Console.WriteLine(contacts[0].LastName +
contacts[0].ModifiedDate);
contacts[0].ModifiedDate = DateTime.Now;
Console.WriteLine(contacts[0].LastName +
contacts[0].ModifiedDate);

t = new Thread(occ.SaveChanges);
t.Start();
}

public static void ResultCallback(List<Contact> contactList)


{
contacts = contactList;
}
}

Ioan Asiminoaei
Entity Framework 27.02.2012 Pag. 16/16

public class ObjectContextClass


{
private BAEntities context;

// Delegate used to execute the callback method when


// the task is done.
private contextCallback callback;

// The callback delegate is passed in to the constructor


public ObjectContextClass(contextCallback callbackDelegate)
{
callback = callbackDelegate;
}

public void GetCustomers()


{
if (context == null)
{
context = new BAEntities();

//put a lock on the context during @this operation;

// E bine acest lock aici? Unde ar fi trebuit sa fie pus?


// Explicati
System.Threading.Monitor.Enter(context);
var contactquery = from c in context.Contacts
where c.LastName.StartsWith("S")
select c;

////unlock the context;


var conList = contactquery.ToList();
System.Threading.Monitor.Exit(context);
if (callback != null)
callback(conList);
}

public void SaveChanges()


{
System.Threading.Monitor.Enter(context);
// ar mai trebui try ... catch ... pentru cod in productie
context.SaveChanges();
System.Threading.Monitor.Exit(context);
}
}
}

Ioan Asiminoaei

S-ar putea să vă placă și