O'Reilly's Programming Entity Framework - Code First
By Julia Lerman & Rowan Miller
- Table name will be plural of class name created with dbo schema.
- Column names same as properties they map to.
- Strings are mapped to nvarchar(max)
- Byte[] is mapped to varbinary(max)
- Bool to bit not null
- A class must have a key property (default named Id or typenameId)
- One-to-Many Relationships with foreign key [Name of navigation property]_[Primary key of related class]
- Foreign keys are nullable by convention.
- In memory metadata created from code or EDMX model (System.Data.Metadata.Edm)
- Only use DetectChanges if needed for performance tuning.
Data Annotations (System.ComponentModel.DataAnnotations) vs Fluent API (modelBuilder.Entity<AnimalType>())
[Table("Species")]
this.ToTable("Species");
[Table("Species", Schema="baga")]
this.ToTable("Species", "baga");
[Column("LocationName")]
this.Property(d => d.Nam).HasColumnName("LocationName");
[Key]
this.HasKey(t => t.Guid);
[Required]
this.Property(p => p.TypeName).IsRequired();
[MinLength(10), MaxLength(500)]
this.Property(p => p.Description).HasMaxLength(500); (no min available)
[Column(TypeName="image")]
this.Property(d => d.Photo).HasColumnType("image");
[DatabaseGeneratedOption.Identity]
this.Property(t => t.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
[Timestamp] must be a byte[]
this.Property(t => t.TimeStamp).IsRowVersion();
[ConcurrencyCheck]
this.Property(t => t.SocialSecurityNumber).IsConcurrencyToken();
[ComplexType]
modelBuilder.ComplexType<Address>();
[ForeignKey("BillingAddressId")]
modelBuilder.Entity<User>().HasRequired(a => a.BillingAddress).WithMany().HasForeignKey(u => u.BillingAddressId);
[Required]
modelBuilder.Entity<PersonPhoto>().HasRequired(p => p.PhotoOf).WithRequiredDependent(p => p.Photo);
[InverseProperty("PrimaryContactFor")]
modelBuilder.Entity<Lodging>().HasOptional(l => l.PrimaryContact).WithMany(p => p.PrimaryContactFor);
this.Property(t => t.SomeAscii).IsUnicode(false);
this.Property(t => t.SomeMiles).HasPrecision(8,1);
foo.WillCascadeOnDelete(false);
[NotMapped]
Ignore(d => d.TodayForecast);
- By default, Code First will create the database only if it doesn't already exist.
- Complex types have no key property, can only contain primitive properties and when used as a property in another class, the property must represent a single instance. It cannot be a collection type. [HostPropertyName_PropertyName]
- Entity.Has[Multiplicity](Property).With[Multiplicity](Property)
- .HasOptional, .HasRequired, .HasMany + WithOptional, WithRequired, WithMany
- [Target Type Key Name], [Target Type Name] + [Target Type Key Name], [Navigation Property Name] + [Target Type Key Name]
var tripWithActivities = context.Trips.Include("Activities").FirstOrDefault();
Table splitting
modelBuilder.Entity<Person>().ToTable("People");
modelBuilder.Entity<PersonPhoto>().ToTable("People");
Entity splitting
Map(m => { m.Properties(d => new { d.Name, d.Country, d.Description }); m.ToTable("Locations"); });
Map(m => { m.Properties(d => new { d.Photo }); m.ToTable("LocationPhotos"); });
- For protected and private properties, the configuration class must be nested inside the class that is part of the model.
- The setter can be marked with a more restrictive accessor, but the getter must remain public for the property to be mapped automatically. (not supported in medium trust because of reflection)
public class Person
{
public int PersonId { get; set; }
private string Name { get; set; }
public class PersonConfig : EntityTypeConfiguration<Person>
{
public PersonConfig()
{
Property(b => b.Name);
}
}
public string GetName()
{
return this.Name;
}
public static Person CreatePerson(string name)
{
return new Person { Name = name };
}
}
modelbuilder.Configuration.Add(new Person.PersonConfig());
public string FullName
{
get { return String.Format("{0}{1}",FirstName.Trim(), LastName); }
}
Table per hierarchy (TPH) describes mapping inherited types to a single database table that uses a discriminator column to differentiate one subtype from another. This is default for Entity Framework. (nvarchar(128), not null containing the type name)
- Map(m => { m.ToTable("Lodgings"); m.Requires("LodgingType").HasValue("Standard"); });
- Map<Resort>(m => { m.Requires("LodgingType").HasValue("Resort"); });
- Map(m => { m.ToTable("Lodgings"); m.Requires("IsResort").HasValue(false); });
- Map<Resort>(m => { m.Requires("IsResort").HasValue(true); });
Table per type (TPT) only stores properties for base class in a single table. Additional properties are stored in separate table.
- modelBuilder.Entity<Resort>().ToTable("Resorts");
- modelBuilder.Entity<Lodging>().Map<Resort>(m => { m.ToTable("Resorts"); });
Table per concrete type (TPC) stores all the properties for each type in a separate table. Map inheritance to tables with common overlapping fields.
- modelBuilder.Entity<Lodging>().Map(m => { m.Table("Lodgings"); }).Map<Resort>(m => { m.ToTable("Resorts"); m.MapInheritedProperties(); } );
Mapping foreign keys
- HasRequired(l => l.Destination).WithMany(d => d.Lodgings).Map(c => c.MapKey("destination_id"));
- HasRequired(l => l.Destination).WithMany(d => d.Lodgings).Map(c => c.MapKey("destination_id").ToTable("LodgingInfo"));
- HasMany(t => t.Activities).WithMany(a => a.Trips).Map(c => c.ToTable("TripActivities"));
- HasMany(t => t.Activities).WithMany(a => a.Trips).Map(c => { c.ToTable("TripActivities"); c.MapLeftKey("TripIdentifier"); c.MapRightKey("ActivityId"); });
- MapLeftKey affects the foreign key column that points to the class being configured!
Connections
// you need to take care of disposing the connection unless your context owns the connection (property)
using (var connection = new SqlConnection(connectionString))
{
using (var db = new MyDbContext(connection)) { ... }
using (var dbUsingSameConnection = new MyDbContext(connection)) { ... }
}
- SqlConnectionFactory : IDbConnectionFactory
- Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Initialization is triggered the first time that the context is used. Initialization occurs lazily.
- Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BreakAwayContext>());
- Database.SetInitializer(new CreateDatabaseIfNotExists<BreakAwayContext>());
- Database.SetInitializer(new DropCreateDatabaseAlways<BreakAwayContext>()); // use system.Transactions.TransactionScope if you have a lot of integration tests
- EFCodeFirst.CreateTablesOnly nuget package
using (var context = new BreakAwayContext())
{
try
{
context.Database.Initialize(force: false);
}
catch (Exception ex) { ... }
}
Database.SetInitializer(null); // do not magically create a database
<appSettings>
<add key="DatabaseInitializerForType DataAccess.BreakAwayContext, DataAccess" value="Disabled" />
</appSettings>
public class PromptForDropCreateDatabaseWhenModelChanges<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
var exists = context.Database.Exists();
if (exists && context.Database.CompatibleWithModel(true)) return;
if (exists)
{
// prompt
context.Database.Delete();
}
context.Database.Create();
}
}
public class DropCreateBreakAwayWithSeedData : DropCreateDatabaseAlways<BreakAwayContext>
{
protected override void Seed(BreakAwayContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_Lodgings_Name ON Lodgings (Name)");
context.Destinations.Add(new Destination { Name = "foo" });
base.Seed(context);
}
}
Mapping to Updatable views
Using views to populate objects
var destinations = context.Destinations.SqlQuery(@"SELECT Id AS DestinationId, Name FROM dbo.TopTenDestinations");
public class DestinationSummary
{
public int DestinationId { get; set; }
public string Name { get; set; }
public int ResortCount { get; set; }
}
var summary = context.Database.SqlQuery<DestinationSummary>("SELECT * FROM dbo.DestinationSummaryView");
Stored Procedures
var country = "Belgium";
var keywords ="sun, yeahright";
var destinations = context.Database.SqlQuery<DestinationSummary>("dbo.GetDestinationSummary @p0, @p1", country, keywords);
Removing Conventions
- modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Model Caching
- Using the same DbCompiledModel to create contexts against different types of database servers is not supported. Instead, create a separate DbCompiledModel for each type of server being used.
var sql_model = GetBuilder().Build(new DbProviderInfo("System.Data.SqlClient", "2008")).Compile();
var context = new BreakAwayContext (connection, sql_model);
public static DbModelBuilder GetBuilder()
{
var builder = new DbModelBuilder();
builder.Entity<EdmMetadata>().ToTable("EdmMetadata");
...
builder.Entity<Trip>();
return builder;
}
EdmMetadata Table
- Snapshot of your model as a SHA256 hash.
- var modelHash = EdmMetadata.TryGetModelHash(context);
- var databaseHash = context.Set<EdmMetadata>().Single().ModelHash;
- var compatible = context.Database.CompatibleWithModel(throwIfNoMetadata:true);
- modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
More