Entity Framework Code First
Links
- ADO.Net team introduction
- MSDN for EF 4.1/4.2
- MVC Mini-profiler
- My blog entry on using using the EF provider wrappers for SQL logging a DbContext
- EF Code First generic repository
- EF Code First Databases
You can't have a code first model in the same project as an EDMX model.
DbContext (Unit of Work)
DbContext = EF 4 ObjectContext
- Default ctor: uses the namespace qualified name of the class (or, if app.config defines one, a connection with name or namespace.name)
- ctor(string): uses the connection string with that name (also: "name=MyDb")
- The context name should match the connection string name (if the database doesn't exist, it creates it - default SqlExpress- and adds an EdmMetadata table)
It's better to expose DbSets as IDbSet. DbSet properties can be readonly: get { return Set<T>(); }
public IDbSet<Product> Products
{
get { return Set<Product>(); }
}
DbSet
DbSet<T> = EF 4 ObjectSet
| Query | set.Find(1) //Get by id (from 1st level cache, then store, or null) |
| Insert | set.Add(entity) |
| Update |
Either load and change... var entity = context.Products.Find(1); (DO NOT set.Add ... you'll create a new entity) ... OR... //create a detached entity and populate *all* fields |
| Attach |
var entity = new Product(); |
| InsertOrUpdate |
You need to know the primary key. public void InsertOrUpdate(DbContext context, Station entity) |
Exceptions
- DbEntityValidationException - validation
- DbUpdateConcurrencyException - concurrency
- DbUpdateException - updates (sometimes validation)
try
{
context.SaveChanges();
}
catch (System.Data.Entity.Validation.DbEntityValidationException validationException)
{
foreach (var error in validationException.EntityValidationErrors)
{
var entry = error.Entry;
foreach (var err in error.ValidationErrors)
{
Debug.WriteLine(err.PropertyName + " " + err.ErrorMessage);
}
}
}
catch (DbUpdateConcurrencyException concurrencyException)
{
//assume just one
var dbEntityEntry = concurrencyException.Entries.First();
//store wins
dbEntityEntry.Reload();
//OR client wins
var dbPropertyValues = dbEntityEntry.GetDatabaseValues();
dbEntityEntry.OriginalValues.SetValues(dbPropertyValues); //orig = db
}
catch (DbUpdateException updateException)
{
//often in innerException
if (updateException.InnerException != null)
Debug.WriteLine(updateException.InnerException.Message);
//which exceptions does it relate to
foreach (var entry in updateException.Entries)
{
Debug.WriteLine(entry.Entity);
}
}
Lazy/Eager loading
- Lazy load with .Include(x=>x.Reference)
- Collections can be non-lazy if they are not virtual, or in the context Configuration.LazyLoadingEnabled = false;
- Eager loading:
context.Entry(entity).Reference(x => x.Reference).Load();
context.Entry(entity).Collection(x => x.Children).Load(); - Filtered loads:
context.Entry(entity)
.Collection(x => x.Children)
.Query()
.Where(x=> x.DateOfBirth > DateTime.Now)
.Load();
var count = context.Entry(entity)
.Collection(x => x.Children)
.Query()
.Count();
//load all into memory
context.Categories.Load();
//get all without tracking (NOT in dbContext)
var cachableCategories = context.Categories.AsNoTracking().ToList();
Other DbSet properties
- context.Categories.Local - an ObservableCollection of the first level cache.
//what's changed...
foreach (var dbEntityEntry in context.ChangeTracker.Entries<Category>())
{
Debug.WriteLine(dbEntityEntry.State);
}
Entities
- Entities should have a primary key (by default "Id" or "classId" or [Key] attribute)
- Can be lazy loading proxies or change tracking proxies depending on entity definition and how they are created.
- For simple lazy loading proxies, only navigation properties have to be virtual.
- For change tracking proxies (IEntityWithChangeTracker) ALL properties must be virtual (so everything is tracked), and navigation properties must be virtual ICollection<T>. NOT IList<T>
- For change tracking proxies, navigation collections are EntityCollection<T> at runtime. If your code initializes them (as List<T> in the constructor, for instance), you get a runtime error. This makes them awkward for use in test vs runtime code as the collection magically appears when using CodeFirst.
- If context.Configuration.ProxyCreationEnabled = true (default) proxies are created over the pocos for tracking.
- You can also have readonly non-tracking) entities, use DbSet.AsNoTracking() -
eg context.Products.AsNoTracking().Where(x => x.CategoryId == 2) (using System.Data.Entity)
To get a proxy, you can't new it up, use DbSet.Create() - eg context.Products.Create().
Also you can create derived classes: context.Products.Create<FoodProduct>()
//create a proxy (NOT ADDED TO SET)
var entity = context.Categories.Create();
Change tracking
For POCOs/lazy loading proxies, DetectChanges is used (implicitly or explicitly); change tracking proxies have it built in.
DbEntityEntry<Category> entry = context.Entry(entity);
//entry.State
var prop = entry.Property(x => x.Name);
//prop.IsModified;
//prop.OriginalValue vs prop.CurrentValue
entry.GetDatabaseValues(); //force a db update
//automap
var clone = new Category();
context.Entry(entity).CurrentValues.SetValues(clone);
Raw SQL
//can grab DbSet entities (by default tracked)
//or any type incl. primitive types
context.Categories.SqlQuery("select * from categories"); //only DbSet, not IDbSet
var categoriesByRawSql = context.Database.SqlQuery<Category>(
"exec mysproc @p1, @p2",
new SqlParameter("p1", 1),
new SqlParameter("p2", 2));
context.Database.ExecuteSqlCommand("drop database");