Differences of Entity Framework 6 and Entity Framework Core with Examples

Entity Framework Core was released with MVC Core, now the latest version is 3.0. Microsoft is not going to retire Entity Frame 6 yet.

The main benefit of Dotnet Core is the supporting of Linux platform release. Linux support has big impact for supporting Internet of Things (IoT), since SSD card mostly only support Linux operation system. Another benefit is the ability to run on different cloud environments.

The book from Adam Freeman (Who published many books of .net, javascripts, mobile, etc), has good coverage for Entity Framework Core: Pro Entity Framework Core 2 for ASP.NET Core MVC

While new features will only be added to EF Core, some EF 6 features are not added to EF Core yet. Here is a document from Microsoft: Compare EF Core & EF6.

Here lists the differences below. Please click the title of each design pattern if you want to know more details.

Features only supported in EF 6:

Create model from database: VS wizard

    This is for database first development. Entity Models are generated from existing database by add ADO.NET Entity Data Model to the project. There are two ways to generate the models: creating an EDMX file, or using EF 6.x DbContext Generator to generate POCO model classes.

Entity splitting

    Entity splitting maps one data model class to multiple database tables. It's useful for data that is stored in multiple tables with 1 to 1 or 1 to 0 relationship. For example you can have a employee table, also employee address and employee spouse table.
    To create the mapping, you override OnModelCreating method in you DBContext class, map a group of properties to each tables.
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
            modelBuilder.Entity<Employee>()
                    .Map(map =>
                    {
                        map.Properties(p => new
                        {
                            p.EmployeeId,
                            p.Name,
                            p.EmployeeNumber
                        });
                        map.ToTable("Employee");
                    }).Map(map =>
                    {
                        map.Properties(p => new
                        {
                            p.EmployeeId,
                            p.Address,
                            p.City,
                            p.State
                        });
                        map.ToTable("EmployeeAddress");
                    }).Map(map =>
                    {
                        //Another table
                    }
    }
        

Many-to-many without join entity

    For example, we have ObjectA, ObjectB and ObjectAObjectB for a many to many relatioship. There're 2 ways of many to many mapping:
    1) By convention
    Create ObjectA and ObjectB, define a collection of ObjectB in ObjectA, and define a collection for ObjectA in ObjectB.
    public class ObjectA
    {
        .......
        public virtual ICollection<ObjectB> ObjectBs { get; set; }
    }
            
    public class ObjectB
    {
        .......
        public virtual ICollection<ObjectA> ObjectAs { get; set; }
    }
    

    2) By using Fluent API Override OnModelCreating method in DBContext class. Join ObjectA and ObjectB with object ObjectAObjectB.
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    
        modelBuilder.Entity<ObjectA>()
                    .HasMany<ObjectB>(s => s.ObjectBs)
                    .WithMany(c => c.ObjectAs)
                    .Map(cs =>
                            {
                                cs.MapLeftKey("ObjectARefId");
                                cs.MapRightKey("ObjectBRefId");
                                cs.ToTable("ObjectAObjectB");
                            });
    }
    

    In EF Core, you have to explicitly define a join class ObjectAObjectB and map like this:
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ObjectAObjectB>()
            .HasKey(pc => new { pc.ObjectAId, pc.ObjectBId });
    
        modelBuilder.Entity<ObjectAObjectB>()
            .HasOne(pc => pc.ObjectA)
            .WithMany(p => p.ObjectAObjectBs)
            .HasForeignKey(pc => pc.ObjectAId);
    
        modelBuilder.Entity<ObjectAObjectB>() .HasOne(pc=> pc.ObjectB)
            .WithMany(c => c.ObjectAObjectBs)
            .HasForeignKey(pc => pc.ObjectBId);
    }
    

Inheritance: Table per type (TPT)

    In TPT, shared properties are defined in a superclass table and subclass tables only define keys and unique properties for each subclass. The advantage of TPT is normalized schemas. The disadvantages are performance and query complexity. Create views can simplify the complexity for TPT.
    By default, Entity Framework uses Table per hierachy (TPH), that super class and subclasses data share the same table. There is a type column (Discriminator property) holding the values indicate different subclasses. Different subclasses fill data in different groups of nullable columns. The advantage of TPT is performance. The disadvantage of TPH is denormalized schemas.
    In code-first approach, it can be implemented by adding table name attribute to the class, or subclass mapping in Fluent API.
    public abstract class SuperClass
    {
        ....
    }
     
    [Table("TableA")]
    public class SubClassA : SuperClass
    {
        .....
    }
     
    [Table("TableB")]
    public class SubClassB : SuperClass
    {
        .....
    }
     
    public class InheritanceMappingContext : DbContext
    {
        public DbSet<SupperClass> SupperClasses { get; set; }
    }
    

    Using Fluent API:
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<SubClassA>().ToTable("TableA");
        modelBuilder.Entity<SubCLassB>().ToTable("TableB");
    }
    

Inheritance: Table per concrete class (TPC)

    In TPC, there's no superclass table, each subclass has its own table. TPC is not supported by Visual Studio designer, so some people discourage to use it. It can be implemented in code first approach by calling fluent API MapInheritedProperties in OnModelCreating method.
    public abstract class SuperClass
    {
        .....
    }
            
    public class SubclassA : SuperClass
    {
        .....
    }
            
    public class SubclassB : SuperClass
    {
        .....
    }
        
    public class InheritanceMappingContext : DbContext
    {
        public DbSet SuperClasses { get; set; }
            
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<SubClassA>().Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("TableA");
            });
     
            modelBuilder.Entity<SubclassB>().Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("TableB");
            });            
        }
    }
    

Graphical visualization of model

    This is about entity data model (.edmx) that can be edited using design tools. ADO.NET entity data model (.edmx) is not supported in Entity Framework Core.

Graphical model editor

    This is the similar topic about entity data model (.edmx) as above.

Model format: EDMX (XML)

    This is the similar topic about entity data model (.edmx) as above.

Update model from database

    This is the similar topic about entity data model (.edmx) as above. Open your .edmx file to display the model diagram. Right-click anywhere on the design surface, and select Update Model from Database.

Text-based query language (Entity SQL)

    Entity SQL is similar with SQL to query conceptual models in the Entity Framework
    string eSqlQuery = @"SELECT VAlUE b FROM BillingDetails AS b";
    ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
    ObjectQuery objectQuery = objectContext.CreateQuery(eSqlQuery);
    List billingDetails = objectQuery.ToList();
    

Stored procedure mapping

    This is about code-first approach. Map a model by calling MapToStoredProcedures,
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity()
                        .MapToStoredProcedures();
        }
    
    then 3 stored procedures matching naming convension will be automatically created:
    sp_InsertStudent, sp_UpdateStudent and sp_DeleteStudent
    You can also pass your own stored proedure names if you don't want to use the default ones.

Lifecycle hooks (events, interception)

    Anytime Entity Framework sends a command to the database this command can be intercepted by application code.
    Define an interceptor class implementing IDbCommandInterceptor:
                    public class NLogCommandInterceptor : IDbCommandInterceptor
                    {
                        .....
                    }
                
    Add the interceptor class to DbInterception:
                    DbInterception.Add(new NLogCommandInterceptor());
    
    Then NLogCommandInterceptor will get every command pass to the database.


Features only supported in EF Core:

Alternate keys

    An alternate key serves as an alternate unique identifier for each entity instance in addition to the primary key. This helps ensure the uniqueness of data. It's defined in OnModelCreating in DBContext class.
    You can specify it when you define the relationship by calling HasPrincipalKey:
            modelBuilder.Entity<Post>()
                .HasOne(p => p.Blog)
                .WithMany(b => b.Posts)
                .HasForeignKey(p => p.BlogUrl)
                .HasPrincipalKey(b => b.Url);
    
    Or specify it seperately in Fluent API by calling HasAlternateKey:
            modelBuilder.Entity<Car>()
                .HasAlternateKey(c => c.LicensePlate);
    

Batch updates

    EF Core generates SQL statements to run database commands based on your application code. You can set maximum batch size when you create DBContext.
            context.ClassEntries
                .Where(te => ClassEntryIds.Contains(te.Id))
                .Update(te => new ClassEntry() { RecordId = record.Id });
    
    Or you can use .UpdateAsync
    You can also intercepts the DbCommand with another command before update is executed
            context.ClassEntries
                .Where(te => ClassEntryIds.Contains(te.Id))
                .Update(te => new ClassEntry() { RecordId = record.Id },
                        x => { x.Executing = command => commandText = command.CommandText; });
    

Mixed client/database evaluation in LINQ queries

    In Linq, you can call a static C# method,
    var blogs = context.Blogs
        .Select(blog => new
        {
            Id = blog.BlogId,
            Url = ToTitleCase(blog.Title)
        })
        .ToList();
    
    This make every word capitalized,
    public string ToTitleCase(string str)
    {
        return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
    }
    
    But be careful to use client evaluation in where statement, this can largely slow down the performance.

Loading related data: Eager loading for derived types

    Load Persons and include Compensation for derived type Employees
    var employees = context.Persons.OfType<Employee>().Include(x => x.Compensation).ToArray();
    
    var nonEmployees = context.Persons.Except(context.Persons.OfType<Employee>()).ToArray();
    
    var people = employees.Concat(nonEmployees);
    

SQL queries: Composing with LINQ

    Raw SQL can be executed on entity objects. But the SQL return attributes must matching entity properties.
    var blogs = context.Blogs
        .FromSql("SELECT * FROM dbo.Blogs")
        .ToList();
    
    var blogs = context.Blogs
        .FromSql("EXECUTE dbo.GetMostPopularBlogs")
        .ToList();
    

Constructors with parameters

    Model class can have a constructor with parameters. It does not require all properties passed to the constructor.
    public class Blog
    {
        public Blog(int id, string p1, string p2)
        {
            Id = id;
            P1 = p1;
            P2 = p2;
        }
    
        public int Id { get; set; }
    
        public string P1 { get; set; }
        public string P3 { get; set; }
        public string P3 { get; set; }
    }
    

Property value conversions

    In OnModelCreating method of DBContext class, you can use value conversions to define a lookup for a property of an entity, or encrypt data before save to database.
    Encrypt data:
    class Login
    {
    	public int Id { get; set; }
    	[Encrypted]
    	public string Secret { get; set; }
    }
    
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    sealed class EncryptedAttribute : Attribute
    { }
    
    class MyContext : DbContext
    {
        .....
    	protected override void OnModelCreating(ModelBuilder modelBuilder)
    	{
    		base.OnModelCreating(modelBuilder);
    
    		modelBuilder.Entity<Login>()
    			.Property(x => x.Secret).HasMaxLength(20);
    
    		foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    		{
    			foreach (var property in entityType.GetProperties())
    			{
    				var attributes = property.PropertyInfo.GetCustomAttributes(typeof(EncryptedAttribute), false);
    				if (attributes.Any())
    				{
    					property.SetValueConverter(new EncryptedConverter());
    				}
    			}
    		}
    	}
    }
    
    class EncryptedConverter : ValueConverter
    {
        public EncryptedConverter(ConverterMappingHints mappingHints = default)
        : base(EncryptExpr, DecryptExpr, mappingHints)
        { }
    
        static Expression<Func<string, string>> DecryptExpr = x => new string(x.Reverse().ToArray());
        static Expression<Func<string, string>> EncryptExpr = x => new string(x.Reverse().ToArray());
    }
    
    Convert data using lookup:
    public class Rider
    {
        public int Id { get; set; }
        public EquineBeast Mount { get; set; }
    }
    
    public enum EquineBeast
    {
        Donkey,
        Mule,
        Horse,
        Unicorn
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity()
            .Property(e => e.Mount)
            .HasConversion(
                v => v.ToString(),
                v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
    }
    

Mapped types with no keys (query types)

    This can be used for tables with no identity, views or structured type created by clr, etc. This is a stepping stone to enable ad-hoc mapping for arbitrary types. This is an example to map to a view in database:
    modelBuilder
            .Query<BlogPostsCount>().ToView("View_BlogPostCounts")
    

Shadow state properties

    Shadow state properties are not defined in your model class, but are maintained by EF Core change tracker. It can be used for change tracking, like "LastUpdated" field, or foreign key reference.
    For example BlogId can be automatically included in Post Entity. But if you explicitly define BlogId as below, it will use the matching shadow property instead of creating a new property.
    public class Post
    {
        public int PostId { get; set; }
        ........
        public Blog Blog { get; set; }
    }
    

Key generation: Client

    This is used when you need to know the key before insert data into a table. Hi/Lo algorithm is to generate a key that part of it from database, part of it from C# code and combine it. The Hi/Lo algorithm is useful when you need unique keys before committing changes. EF Core supports HiLo with the ForSqlServerUseSequenceHiLo method.

Global query filters

    Global query filters can be applied to entities when model is created. A good example is filter out data with soft delet flags.
        modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted);
        base.OnModelCreating(modelBuilder);
    

Field mapping

    You can use Data Annotations to configure the column to which a property is mapped. This is helpful when you have different coding convention for database entity and C# entities.
    Use Data Annotations,
        [Column("customer_id")]
        public int CustomerId { get; set; }
    
    or Fluent API,
        modelBuilder.Entity()
            .Property(b => b.CustomerId)
            .HasColumnName("customer_id");
    

Jet (Microsoft Access)

    Microsoft Access and Visual Basic use or have used Jet as their underlying database engine. It has been superseded for general use.

In-memory (for testing)

    This is for testing purpose. It has some convenience like, it allows you to save data that would otherwise violate referential integrity constraint.
    var options = new DbContextOptionsBuilder ()
                .UseInMemoryDatabase(databaseName: "My_in_memory_database")
                .Options;