Entity Framework v4.1-v6 Code First
EF v4.1 with Code First was released in 2011. EF v6 was released in 2013.
EF v6 was still the recommended EF after EF Core v1 (originally EF7) was released in 2016 (EF Core was missing some critical features, including lazy loading)
Links
- ADO.Net team introduction )blogs.msdn.com)
- MSDN
- Glimpse has MVC plugins for profiling
- 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();
WCF/WebAPI may have problems with proxies so:
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.LazyLoadingEnabled = false;
You may have to have [System.Runtime.Serialization.IgnoreDataMember] on association properties to foil the DataContractSerializer.
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");