Entity Framework Database/Model First
See EF Code First
Links
- Asp.Net tutorials
- MSDN
- Provider wrappers for logging/caching (or use Ayende's EF Profiler)
General
Add New Item - ADO.NET Entity Data Model
Uses a EDM (Entity Data Model) comprising conceptional (csdl), store (ssdl) and mapping (msl) xml.
- Database first: like Linq2Sql
- Model First: Designer, then use Generate Database wizard from designer.
- In code: context.CreateDatabase() and context.CreateDatabaseScript()
- EF 4.0 has poco support (EDMX file/mapping with POCOs)
- EDMX designer right click - Add Code Generation Item- POCO (or Entity Tracking) T4 templates
- Clear the EDMX file's CustomTool, hand-code the POCOs and an ObjectContext
- Code First (in EF 4.1): Just write POCOs like NHibernate, and the context object derives from DbContext with DbSet<Entity> collections.
ObjectContext
ObjectContext is the equivalent of Linq2Sql DataContext/ NHibernate Session. The default generated class ends in "Entities" (e.g. NorthwindEntities). The collections are ObjectSet<Entity> (NB: fk collections on an entity are EntityCollections).
Connection strings use the provider System.Data.EntityClient with res://*/model.csdl values (res://*/ wildcard uses the calling assembly & bin dlls; otherwise specify the assembly fullname including public key etc). The actual ADO connection string is embedded as a value inside the metadata.
Mapping
You can code functions into the EDMX xml (in edmx:ConceptualModels/Schema - Function /Parameter+DefiningExpression) and a class method with a [EdmFunctionAttribute] (which is not implemented - throw an exception). When the function is called in a Linq query, it's transformed into sql and executed server side.
Complex Types
Eg an Address object on a Person/Company etc (no inheritance. no navigation). Select the properties and "Refactor Into New Complex Type" or in Model Browser "Create New Complex Type". Complex type properties can't be null (SaveChanges() throws an InvalidOperationException)
Inheritance
- Table per hierarchy (single table) - one table with a discriminator column
- In designer, just Add Entity and set Base Type- move specific columns (and mapping) to derived classes.
- In mapping details for derived class, add Condition (discrimator column = value)
- Remove the discrimator column from the base class model.
- MSDN
- Table per type - multiple tables for base class and each derived class (i.e. looks like associations)
- On derived types, set the BaseType, delete the associations and the shared primary key
- MSDN
- Table per concrete class (but not for abstract base type) - i.e. duplicated columns
- There is no designer support, but you can create the abstract base type and fix the EDMX xml manually (ugh)
Entities
There are many sorts:
- Entities derived from EntityObject (default Database First/ Model First).
- They have tracking (exposed in property .EntityState)
- Note: a new entity has a state of Detached (and thus is not saved) until it's added via ObjectSet.AddObject() or for fk collection Add()).
- NB: to set the generated class namespace, it's EDMX file's Custom Tool Namespace.
- 0.1 FKs are EntityReference<T>
- 1.* FKs are EntityCollection<T>
- Entity Tracking entities (a special T4 template for disconnected scenarios)
- POCOs (via T4 template or hand coded/ Code First).
Lazy and Eager Loading
Lazy loading is enabled on EDMX level.
var orders = context.Orders
.Include("Order_Details")
.Where("it.OrderDate > @startDate", new ObjectParameter("startDate", startDate));
Or load a fk collection: order.Order_Details.Load();
Object Queries
NHibernate has a confusing range of query methods (HQL, Criteria, QueryOver, Query/Linq). Entity Framework has linq to entities, Query builder and Entity SQL.
The ObjectContext collection properties are ObjectSet<Entity> (extends ObjectQuery) which allows you to build Object Queries (you can use IQueryable as in Linq2Sql). ObjectQueries have standard methods including Where, Select, OrderBy, GroupBy etc - note that standard Linq versions may throw exceptions (eg using a IComparer in OrderBy) unless you turn into client side objects (AsEnumerable) first.
//Option 1: IQueryable works but you lose the ObjectSet
var orders = context.Orders
.Where(x => x.OrderDate > startDate);
//Option 2: Chaining the ObjectSet
var orders2 = context.Orders
//entitySql clause as string with @prefixed parameter.
//orders2.Parameters is merged, so be wary of name collisions when building queries
.Where("it.OrderDate > @startDate", new ObjectParameter("startDate", startDate));
//default alias: orders2.Name == "it";
//with ObjectSet you get a few other nice things
var sql = orders2.ToTraceString(); //tracing
var results = orders2.Execute(MergeOption.AppendOnly); //execute immediately
Entity SQL
You can use an EntityCommand with EntitySQL(ESQL- EF's version of HQL).
//reference a defined connection string with name = x
using (var conn = new EntityConnection("name = NorthwindEntities"))
{
conn.Open();
const string esql = "SELECT c.CategoryId, c.CategoryName FROM NorthwindEntities.Categories AS c";
using (var cmd = new EntityCommand(esql, conn))
{
//When getting a stream of rows, include CommandBehavior.SequentialAccess.
var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
{
var id = reader.GetInt32(0);
var name = reader.GetString(1);
list.Add(id, name);
}
}
}
//or
using (var context = new ObjectContext("name = NorthwindEntities"))
{
context.Connection.Open();
const string esql = "NorthwindEntities.Categories";
var result = new ObjectQuery<Category>(esql, context);
var first = result.First();
var id = first.CategoryID;
var name = first.CategoryName;
}
Or use an ObjectContext with an ObjectQuery
using (var context = new ObjectContext("name = NorthwindEntities"))
{
context.Connection.Open();
const string esql = "SELECT c.CategoryId, c.CategoryName FROM NorthwindEntities.Categories AS c";
var result = new ObjectQuery<DbDataRecord>(esql, context);
var first = result.First();
var id = first.GetInt32(0);
var name = first.GetString(1);
}
For complex properties, the DbDataRecord field is actually a DbDataRecord itself.
ESQL | Explanation |
---|---|
SELECT p.Cost FROM NorthwindEntities.Products AS p |
Always use aliases (or the implicitly use the table name) There's no SELECT p.* either. |
SELECT ROW(p.Cost AS ProdCost, p.Name AS ProdName) FROM NorthwindEntities.Products AS p |
Create collection of anonymous types (ProdCost, ProdName). |
SELECT VALUE p FROM NorthwindEntities.Products AS p [WHERE...] |
Selects an entity (or primitive) value into the result collection. (SQL Select * from Products returns a DbRow of columns; "SELECT VALUE" returns an ICollection<T>). You can only "SELECT VALUE" one thing. (SELECT VALUE AVG(p.Cost) FROM...) |
SELECT VALUE CAST(p.Cost AS Edm.Int32) FROM NorthwindEntities.Products AS p |
Casting to different types |
SELECT VALUE Edm.Count(0) FROM NorthwindEntities.Products AS p |
There are various cross platform canonical functions such as AVG, Count, Sum, CurrentDateTime(), Contains, EndsWith, Length, Replace, Trim, ToUpper... |
SELECT VALUE p FROM NorthwindEntities.Products AS p ORDER BY o.Cost SKIP 20 LIMIT 10 |
Paging- ORDER BY x SKIP y LIMIT z |
SELECT REF(p) FROM NorthwindEntities.Products AS p |
Gets an EnityKey (just the key) instead of the full object. You can DEREF() to go back to the full object. |
Native SQL
- context.ExecuteStoreCommand(sql) (returns rows affected)
- context.ExecuteStoreQuery<T>(sql, parameters).
- Can have no type, a simple type (int), or a class whose properties are auto-mapped to the result columns
- Parameters may be {0} esql format, or @x format with primitive types or the native SqlParameters.
Updates
See here
var context = new NorthwindEntities();
var choc = new Category { CategoryName = "Chocolate", Description = "Choco" };
//choc.EntityState == EntityState.Detached
context.Categories.AddObject(choc);
//choc.EntityKey.IsTemporary == true
//choc.EntityState == EntityState.Added
context.SaveChanges();
//choc.EntityKey.IsTemporary == false
//choc.EntityState == EntityState.Unchanged
choc.Description = "Chocolate related products";
//choc.EntityState == EntityState.Modified
context.SaveChanges();
context.Categories.DeleteObject(choc);
//choc.EntityState == EntityState.Deleted
context.SaveChanges();
//choc.EntityState == EntityState.Detached
You can find the status of an object via context.ObjectStateManager.GetObjectStateEntry(entity).State. Non-proxied POCOs may not have state so there's a ObjectStateManager.TryGetObjectStateEntry - you can call context.DetectChanges() (context.SaveChanges() does it automatically) for snapshot checking.
Concurrency
Use context.Refresh(RefreshMode.ClientWins, entity) (or StoreWins) to update. You can use this when trapping a SaveChanges() which throws an OptimisticConcurrencyException.
Cascading Deletes
Set the entity EDMX Delete property to Cascade. NOTE: You must .Load or .Include the foreign keys before the DeleteObject(entity) (and sub-dependencies!)
Disconnected entities
You can context.Detach(entity) and attach as follows. The attached entity has status Unchanged
- context.Attach(entity) (or AddObject(entity) if new)
- context.ObjectSet.Attach(entity) (or AddObject(entity) if new)
- entity.ForeignKeyCollection.Attach(entity)
For POCOs, call context.DetectChanges().
To copy scalar properties context[.ObjectSet].ApplyCurrentValues(detached)
Self-tracking entities
It's a T4 item template, intended for disconnected entities (WCF).
They start tracking automatically when deserialized from WCF, or connected to another tracking entity, or StartTracking() is called (also MarkAsAdded() etc). Call StopTracking() to stop.
When receiving the object with changes, add it with the extension method context.Orders.ApplyChanges(order)
Stored procedures
In your ObjectContext, a sproc in the conceptual model can be called by context.ExecuteFunction("name", pars). To add it to the generated context, from Model Browser/ "Function Imports" - "Add Function Import" (you can map to existing entities).
Entity updates can be done by sprocs (but you must have insert, update and delete) - in designer, click entity and Stored Procedure Mapping.
- Store SSDL: add a Schema/Function element
- Mapping MSL: a ModificationFunctionMapping with InsertFunction, UpdateFunction, and DeleteFunction elements
- Conceptual CSDL (returning a complex type): add a FunctionImport