Some weeks ago Microsoft released Beta 3 of the ADO.NET Entity Framework (sometimes called ADO.NET vNext). Probably the final version will be shipped during the first half of 2008 as an update to the Visual Studio 2008 & .NET 3.5 release.
Update 20/10/2008 : Visual Studio 2008 & .NET 3.5 Service Pack 1 has been released in August 2008. This article has been updated so all examples will work fine in the final version of the Entity Framework.
The Entity Framework looks like a interesting technology which is more powerful and advanced than LINQ to SQL. Both technologies have a different kind of philosophy but several features have similar implementations. The EF is more than just an ORM (Object Relational Mapping) tool. It allows developers to query and manipulate data using a conceptual model instead of a physical storage model. It will also become the foundation of new application blocks like Astoria (ADO.NET Data Services) which will enable you to expose any data store as web services and Jasper (Data Access Incubation Projects) which can be used to build dynamic data layers .
This article will not explain all details about the Entity Framework nor compare everything with LINQ to SQL. But I will try to create a small tutorial on how to start with the Entity Framework using the Northwind sample database. If possible I will refer to LINQ to SQL features. This article will be the first in a series about the ADO.NET Entity Framework and the LINQ to Entities querying language.
Part 2 :
Part 3 :
Part 4 :
Download beta 3 of the ADO.NET Entity Framework from the Microsoft website and install it. Make sure you have installed Visual Studio 2008 RTM and the .NET 3.5 framework.
1) Start with a new console application
2) Add a new item. Choose the ADO.NET Entity Model item. Give it the name NorthwindModel.edmx .
3) The Entity Data Model Wizard will be started. Step 1/3 : Choose Model Contents . Choose Generate from database .
4) Step 2/3 : Choose your data connection . Select a connection to the SQL Server Northwind database.
5) Step 3/3 : Choose your database objects . Now you can select all tables, views and stored procedures which should be mapped in your conceptual model.
6) Finish the wizard. The Entity Data Model will be generated. An EDMX file, which is the equivalent of DBML file in LINQ to SQL, will be added to your project.
The Entity Data Model diagram has a lot of similarities with a LINQ to SQL diagram. An EDM entity is divided into 2 parts : the Scalar Properties and the Navigation Properties . A NavigationProperty references another EntityType and it defines the direction of the relationship (one-to-many, one-to-one, ...).
EDM associations use another visualization and they have other properties then LINQ to SQL but they actually represent the same.
Update 20/10/2008 : In Visual Studio 2008 SP1 all properties will be organized by alphabetical order.
LINQ to SQL : Association |
EDM : Association |
By default, LINQ to SQL changes plural table names to singular entity class names automatically. The EDM designer does not do this. So the Northwind table Employees is mapped to an EntityType called Employees. Be careful because this can lead to confusion.
So change the name of the EntityType manually :
After changing the name, you will notice that the diagram is being updated and IntelliSense now correctly shows ObjectQuery<Employee > NorthwindEntities.Employees when hovering the Employees EntitySet in a LINQ to Entities query.
Some simple name convention rules:
If you would open the EDMX file as XML, than you will see that this file contains 3 major sections.
<?xml version="1.0"
encoding="utf-8"
?>
<edmx:Edmx Version="1.0"
xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"
>
<edmx:Runtime>
<!-- CSDL content -->
<edmx:ConceptualModels
>
<Schema Namespace="NorthwindModel"
Alias="Self"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm"
>
<EntityContainer Name="NorthwindEntities"
>
<EntitySet Name="Employees"
EntityType="NorthwindModel.Employee"
/>
<AssociationSet Name="FK_Orders_Employees"
Association="NorthwindModel.FK_Orders_Employees"
>
<End Role="Employees"
EntitySet="Employees"
/>
<End Role="Orders"
EntitySet="Orders"
/>
</AssociationSet>
</EntityContainer>
<EntityType Name="Employee"
>
<Documentation><Summary>Employee entity which corresponds with the Northwind.Employees table</Summary></Documentation>
<Key>
<PropertyRef Name="EmployeeID"
/>
</Key>
<Property Name="EmployeeID"
Type="Int32"
Nullable="false"
/>
<Property Name="LastName"
Type="String"
Nullable="false"
MaxLength="20"
/>
<Property Name="FirstName"
Type="String"
Nullable="false"
MaxLength="10"
/>
<NavigationProperty Name="Orders"
Relationship="NorthwindModel.FK_Orders_Employees"
FromRole="Employees"
ToRole="Orders"
/>
</EntityType>
</Schema>
</edmx:ConceptualModels
>
<!-- SSDL content -->
<edmx:StorageModels
>
<Schema Namespace="NorthwindModel.Store"
Alias="Self"
ProviderManifestToken="09.00.1399"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"
>
<EntityContainer Name="dbo"
>
<EntitySet Name="Employees"
EntityType="NorthwindModel.Store.Employees"
/>
<AssociationSet Name="FK_Orders_Employees"
Association="NorthwindModel.Store.FK_Orders_Employees"
>
<End Role="Employees"
EntitySet="Employees"
/>
<End Role="Orders"
EntitySet="Orders"
/>
</AssociationSet>
</Schema>
</edmx:StorageModels
>
<!-- C-S mapping content -->
<edmx:Mappings
>
<Mapping Space="C-S"
xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS"
>
<EntityContainerMapping StorageEntityContainer="dbo"
CdmEntityContainer="NorthwindEntities"
>
<EntitySetMapping Name="Employees"
>
<EntityTypeMapping TypeName="IsTypeOf(NorthwindModel.Employee)"
>
<MappingFragment StoreEntitySet="Employees"
>
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID"
/>
<ScalarProperty Name="LastName"
ColumnName="LastName"
/>
<ScalarProperty Name="FirstName"
ColumnName="FirstName"
/>
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<AssociationSetMapping Name="FK_Orders_Employees"
TypeName="NorthwindModel.FK_Orders_Employees"
StoreEntitySet="Orders"
>
<EndProperty Name="Employees"
>
<ScalarProperty Name="EmployeeID"
ColumnName="EmployeeID"
/>
</EndProperty>
<EndProperty Name="Orders"
>
<ScalarProperty Name="OrderID"
ColumnName="OrderID"
/>
</EndProperty>
<Condition ColumnName="EmployeeID"
IsNull="false"
/>
</AssociationSetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings
>
</edmx:Runtime>
</edmx:Edmx>
You do not have to modify this XML file manually. The Visual EDM designer and the Mapping Details and Model Browser windows will combine these 3 parts together and give you a nice visual representation of the whole Entity Data Model.
When you build your project, MSBuild will extract the CSDL/SSDL/MSL content from the EDMX file and places these 3 seperate XML files in your project output directory.
The EDM designer has also a nice Mapping Details window. It consists of 2 views.
This view shows all fields in the database and the corresponding properties in the entity. It can be used to view and edit mappings in the Entity Data Model.
This view can be used to select a specialized stored procedure to insert, update or delete an instance of the entity.
In addition to this XML schema file, the wizard has also generated entity classes. Let's take a closer look at these classes in the .Designer.cs file and compare them with the LINQ to SQL classes.
// LINQ to SQL
[Table(Name="dbo.Employees"
)]
public
partial
class
Employee
: INotifyPropertyChanging, INotifyPropertyChanged
An EDM class uses different attributes and it is always inherited from EntityObject or ComplexObject. The EntityObject class provides change tracking and relationship management. Of course this class also inherits from the INotifyPropertyChanging and INotifyPropertyChanged interfaces so all entities support two-way binding.
// Entity Data Model
[global
::System.Data.Objects.DataClasses.EdmEntityTypeAttribute
(NamespaceName="NorthwindModel"
, Name="Employee"
)]
[global
::System.Runtime.Serialization.DataContractAttribute
()]
[global
::System.Serializable
()]
public
partial
class
Employee
: global
::System.Data.Objects.DataClasses.EntityObject
// LINQ to SQL
public
Employee()
{
this
._Employees = new
EntitySet<Employee>(new
Action
<Employee>(this
.attach_Employees), new
Action
<Employee>(this
.detach_Employees));
this
._EmployeeTerritories = new
EntitySet<EmployeeTerritory>(new
Action
<EmployeeTerritory>(this
.attach_EmployeeTerritories),
new
Action
<EmployeeTerritory>(this
.detach_EmployeeTerritories));
this
._Orders = new
EntitySet<Order>(new
Action
<Order>(this
.attach_Orders), new
Action
<Order>(this
.detach_Orders));
this
._Employee1 = default
(EntityRef<Employee>);
OnCreated();
}
EDM does not generate a constructor like LINQ to SQL does. Instead it creates a factory method with input parameters for all required (not nullable) properties.
// Entity Data Model
public
static
Employee
CreateEmployee(int
employeeID, string
lastName, string
firstName)
{
Employee
employee = new
Employee
();
employee.EmployeeID = employeeID;
employee.LastName = lastName;
employee.FirstName = firstName;
return
employee;
}
// LINQ to SQL
[Column(Storage="_EmployeeID"
, AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY"
, IsPrimaryKey=true
, IsDbGenerated=true
)]
public
int
EmployeeID
{
get
{
return
this
._EmployeeID;
}
set
{
if
((this
._EmployeeID != value
))
{
this
.OnEmployeeIDChanging(value
);
this
.SendPropertyChanging();
this
._EmployeeID = value
;
this
.SendPropertyChanged("EmployeeID"
);
this
.OnEmployeeIDChanged();
}
}
}
The attributes of a public property are different in EDM but the get and set accessors are almost the same.
// Entity Data Model
[global
::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute
(EntityKeyProperty=true
, IsNullable=false
)]
[global
::System.Runtime.Serialization.DataMemberAttribute
()]
public
int
EmployeeID
{
get
{
return
this
._EmployeeID;
}
set
{
this
.OnEmployeeIDChanging(value
);
this
.ReportPropertyChanging("EmployeeID"
);
this
._EmployeeID = global
::System.Data.Objects.DataClasses.StructuralObject
.SetValidValue(value
);
this
.ReportPropertyChanged("EmployeeID"
);
this
.OnEmployeeIDChanged();
}
}
// LINQ to SQL
public
System.Data.Linq.Table<Employee> Employees
{
get
{
return
this
.GetTable<Employee>();
}
}
In LINQ to SQL the GetTable method is called to return a collection of entities. In EDM an Entity SQL query will be executed through a Object Services component which returns an EntitySet of EntityTypes.
// Entity Data Model
[global
::System.ComponentModel.BrowsableAttribute
(false
)]
public
global
::System.Data.Objects.ObjectQuery
<Employee
> Employees
{
get
{
if
((this
._Employees == null
))
{
this
._Employees = base
.CreateQuery<Employee
>("[Employees]"
);
}
return
this
._Employees;
}
}
private
global
::System.Data.Objects.ObjectQuery
<Employee
> _Employees;
// LINQ to SQL
[System.Data.Linq.Mapping.DatabaseAttribute(Name="Northwind"
)]
public
partial
class
NorthwindDataContext
: System.Data.Linq.DataContext
EDM has an equivalent of the LINQ to SQL DataContext, namely an ObjectContext . The ObjectContext class is the primary class for interacting with data as objects that are instances of entity types defined in the Entity Data Model. An ObjectContext can be used to make a connection to the database, to retrieve data, to store the persistent objects and to perform insert, update and delete actions on the database.
// Entity Data Model
public
partial
class
NorthwindEntities
: global
::System.Data.Objects.ObjectContext
The connection string of an ObjectContext refers to the metadata (CSDL/SSDL/MSL files) and the data source (database connection string).
connectionString="metadata=./NorthwindModel.csdl|./NorthwindModel.ssdl|./NorthwindModel.msl;
provider=System.Data.SqlClient;
provider connection string="
Data Source=SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True""
EntityTypes, Associations and Properties in the Entity Data Model have a Documentation property. This is new compared to LINQ to SQL.
These documentation properties will update the XML comment of the generated partial entity classes. This is great because it can be used to generate code documentation help files with SandCastle.
///
<summary>
///
Employee entity which corresponds with the Northwind.Employees table
///
</summary>
///
<KeyProperties>
///
EmployeeID
///
</KeyProperties>
[global
::System.Data.Objects.DataClasses.EdmEntityTypeAttribute
(NamespaceName="NorthwindModel"
, Name="Employee"
)]
[global
::System.Runtime.Serialization.DataContractAttribute
()]
[global
::System.Serializable
()]
public
partial
class
Employee
: global
::System.Data.Objects.DataClasses.EntityObject
Entity SQL (E-SQL) is a text-based, collection-oriented and late-bound query language which is influenced by Transact-SQL. It allows you to create queries on the Entity Data Model. Entity SQL statements can be executed with both Object Services components and Entity Client components. It is quite elaborated and some things can become complicated. In this first article I will only be focusing on the various query techniques. So only simple queries without complex conditions, without associations and without aggregates will be demonstrated.
This example will show how to execute an Entity SQL query that returns a collection of instances of one entity type.
1) First create a Northwind ObjectContext instance.
2) The Entity SQL statements itself are string expressions. In most cases they will be composed as SELECT-FROM query expressions. Use the VALUE keyword in the SELECT clause to indicate that the entity should be flattened to a row.
3) We will use some Object Services components to execute the query. So call the CreateQuery<T>() factory method of the ObjectContext . This will create an ObjectQuery object which represents a query against the store. This query will be formulated through the Entity SQL statement.
4) The Entity Framework uses deferred loading. So the SQL statement is executed at the point your code explicitly needs the data. In this case the query will be executed in the first loop of the ForEach iteration.
NorthwindEntities
context = new
NorthwindEntities
();
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp"
;
var
query = context.CreateQuery<Employee
>(sql);
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1} {2} {3}"
, emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
Instead of using the factory method CreateQuery<T>() you can also create an ObjectQuery object yourself. The second parameter is the object context.
NorthwindEntities
context = new
NorthwindEntities
();
var
sql = "NorthwindEntities.Employees"
;
ObjectQuery
<Employee
> query = new
ObjectQuery
<Employee
>(sql, context);
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1} {2} {3}"
, emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
Let's add a WHERE clause :
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = 'USA'"
;
var
query = context.CreateQuery<Employee
>(sql);
Parameters are variables that are defined outside Entity SQL but in the query statement you have to define these parameter names with the at (@) symbol as a prefix. Parameters have to be created as ObjectParameter objects. They can be added to the ObjectQuery instance.
So let us add a Country parameter :
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = @country"
;
var
query = context.CreateQuery<Employee
>(sql);
query.Parameters.Add(new
ObjectParameter
("country"
, "USA"
));
We can also do the same without using the CreateQuery<T>() factory method.
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = @country"
;
ObjectQuery
<Employee
> query = new
ObjectQuery
<Employee
>(sql, context);
query.Parameters.Add(new
ObjectParameter
("country"
, "USA"
));
ObjectParameter objects can also be passed to the CreateQuery<T>() method.
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = @country"
;
var
query = context.CreateQuery<Employee
>(sql, new
ObjectParameter
("country"
, "USA"
));
A third alternative is to use the Where extension method. Use the keyword it to refer to the current query statement.
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp"
;
var
query = context.CreateQuery<Employee
>(sql)
.Where("it.Country = @country"
, new
ObjectParameter
("country"
, "USA"
));
It is also possible to return a collection of primitive types instead of entity types. Therefore you have to make sure that the SELECT clause only returns 1 value. You also have to specify the primitive type in the CreateQuery method.
var
sql = "SELECT VALUE emp.EmployeeID FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = @country"
;
var
query = context.CreateQuery<int
>(sql, new
ObjectParameter
("Country"
, "USA"
));
foreach
(var
id in
query)
Console
.WriteLine("{0}"
, id.ToString());
var
sql = "SELECT VALUE emp.Country FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.EmployeeID = @id"
;
var
query = context.CreateQuery<string
>(sql, new
ObjectParameter
("id"
, 1));
Console
.WriteLine(query.First());
Besides using Entity SQL, you can also use a query builder method to achieve the same result. Entity SQL provides a SelectValue method which will skip the implicit row construction. Only one item may be specified in a select value clause.
string
country = context.Employees.SelectValue<string
>("it.Country"
, new
ObjectParameter
("id"
, 1)).First();
Console
.WriteLine(country);
It is also possible to do some data shaping and to create ObjectQuery<T> queries which return anonymous types. Just change the SELECT clause and use the DbDataRecord class in the CreateQuery method. The DBDataRecord class was introduced in .NET 1.0 and it provides data binding support for any kind of enumeration.
var
sql = "SELECT emp.LastName, emp.FirstName "
+
"FROM NorthwindEntities.Employees AS emp "
;
var
query = context.CreateQuery<DbDataRecord
>(sql);
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1}"
, emp[0], emp[1]);
var
sql = "SELECT emp.LastName AS FamilyName, emp.FirstName "
+
"FROM NorthwindEntities.Employees AS emp "
;
var
query = context.CreateQuery<DbDataRecord
>(sql);
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1}"
, emp["FamilyName"
], emp["FirstName"
]);
Entity SQL can also be executed through EntityClient components. This needs more plumbing but in some cases it can have advantages.
1) First create an EntityConnection . I have re-used the connectionstring of my Northwind data context. Open this connection.
2) Create an EntityCommand object and pass the Entity SQL statement and connection to it.
3) Create a DbDataReader and loop through the results of the command.
NorthwindEntities
context = new
NorthwindEntities
();
EntityConnection
conn = new
EntityConnection
(context.Connection.ConnectionString);
conn.Open();
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp"
;
EntityCommand
cmd = new
EntityCommand
(sql, conn);
DbDataReader
reader = cmd.ExecuteReader(CommandBehavior
.SequentialAccess);
while
(reader.Read())
{
Console
.WriteLine("{0} {1} {2} {3}"
, reader["EmployeeID"
], reader["LastName"
],
reader["FirstName"
], reader["Country"
]);
}
When using a data reader with sequential access you always have to be careful when accessing data. This should be done in sequential order !
e.g. When you change the order of the members/properties, you will get an InvalidOperationException . "Attempt to read from column ordinal '0' is not valid. With CommandBehavior.SequentialAccess, you may only read from column ordinal '2' or greater. "
Update 20/10/2008 : In Beta 3 all members/properties were ordered according the order in the database. In SP1 all properties are organized by alphabetical order. So in this example you should read Country, EmployeeID, FirstName and LastName.
Data shaping with anonymous types can be done with just the same techniques :
EntityConnection
conn = new
EntityConnection
(context.Connection.ConnectionString);
conn.Open();
var
sql = "SELECT emp.LastName, emp.FirstName "
+
"FROM NorthwindEntities.Employees AS emp"
;
EntityCommand
cmd = new
EntityCommand
(sql, conn);
DbDataReader
reader = cmd.ExecuteReader(CommandBehavior
.SequentialAccess);
while
(reader.Read())
{
Console
.WriteLine("{0} {1}"
, reader["LastName"
], reader["FirstName"
]);
}
Adding parameters can also be done easily. Add parameter names with @-prefixes in the Entity SQL string and create EntityParameter objects. Add them to the EntityCommand object.
EntityConnection
conn = new
EntityConnection
(context.Connection.ConnectionString);
conn.Open();
var
sql = "SELECT VALUE emp FROM NorthwindEntities.Employees AS emp "
+
"WHERE emp.Country = @country"
;
EntityCommand
cmd = new
EntityCommand
(sql, conn);
EntityParameter
param = new
EntityParameter
("country"
, DbType
.String);
param.Value = "USA"
;
cmd.Parameters.Add(param);
DbDataReader
reader = cmd.ExecuteReader(CommandBehavior
.SequentialAccess);
while
(reader.Read())
{
Console
.WriteLine("{0} {1} {2} {3}"
, reader["EmployeeID"
], reader["LastName"
],
reader["FirstName"
], reader["Country"
]);
}
LINQ was introduced in .NET 3.5 and I am still excited about this technology. I prefer querying with LINQ to Entities above Entity SQL. LINQ queries do have some restrictions but they are easier, more natural and in addition they are strong-typed so IntelliSense can help you creating queries.
LINQ to Entities is almost the same as LINQ to Objects and LINQ to SQL. So I will just show 2 basic LINQ to Entities queries. Other examples of LINQ queries can be found in several articles on my website.
NorthwindEntities
context = new
NorthwindEntities
();
string
country = "USA"
;
var
query = from
e in
context.Employees
where
e.Country == country
select
e;
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1} {2} {3}"
, emp.EmployeeID, emp.FirstName, emp.LastName, emp.Country);
NorthwindEntities
context = new
NorthwindEntities
();
var
query = from
e in
context.Employees
select
new
{ e.LastName, e.FirstName };
foreach
(var
emp in
query)
Console
.WriteLine("{0} {1}"
, emp.LastName, emp.FirstName);
I hope that this walkthrough provides a good overview of the Entity Data Model and the various querying techniques that the Entity Framework provides. In the next parts of my series about the ADO.NET Entity Framework I will try to cover more advanced Entity SQL query techniques, viewing SQL statements, eager loading, change tracking, concurrency, ... So stay tuned. If you have any remarks or suggestions, please let me know.