Thursday, November 21, 2024
HomeProductsADO.NET Data ProvidersMigrating Entity Framework 6 projects to Entity Framework Core 1 (EF7)

Migrating Entity Framework 6 projects to Entity Framework Core 1 (EF7)

For the purpose of this tutorial, we will use a test project from the “Entity Framework 6 Support for Oracle, MySQL, PostgreSQL, SQLite, DB2, and Salesforce” blog article. We will update this project so that it could be used with Entity Framework Core 1 (Entity Framework 7).

This is not a full-featured tutorial that completely describes migration of Entity Framework 6 projects to Entity Framework Core (Entity Framework 7). We will describe only several of the possible problems and solutions, which we found when migrating a simple and small project that uses only a small part of Entity Framework 6 features.

As one expects, migration from Entity Framework 6 to Entity Framework Core should have been just the following simple steps:

  • change Target Framework for projects;
  • delete old NuGet package
  • add new NuGet package
  • make some configuration changes (though one can expect some vertical compatibility between different versions of the same ORM, and consider this step unnecessary).

That should have been all the steps. However, in reality there are much more problems than one should expect. And in this article we will describe some of these problems and their solutions.

Mandatory Actions

Here are the mandatory actions we have performed when migrating our sample project:

Target Framework

First, we switched to a newer Visual Studio version (Visual Studio 2013 and Visual Studio 2015 are officially recommended for projects using Entity Framework Core).

Changed Target Framework for the projects to .NET Framework 4.5.1 or higher (this is the minimal version for Entity Framework Core).

NuGet Packages

Then we have uninstalled the Entity Framework 6 NuGet package by executing the following command in Package Manager Console:

Uninstall-Package EntityFramework

We have installed a new NuGet package. For the project that uses SQL Server, this was the EntityFramework.MicrosoftSqlServer package:

Install-Package EntityFramework.MicrosoftSqlServer -Pre

Almost twenty dependent NuGet packages were installed with it.

Namespaces

After installing the necessary packages, we need to replace namespaces in the project code.

Main namespace in Entity Framework 6 is System.Data.Entity, in Entity Framework Core it is Microsoft.Data.Entity.

So we replace the following code everywhere:

using System.Data.Entity;

with:

using Microsoft.Data.Entity;

Entity Framework Provider Registration and Setting Connection String

So, everything is good for now. Let’s register the Entity Framework provider.

In Entity Framework 6, providers are usually registered in the application config file (app.config/web.config). Provider factory (for third-party ADO.NET providers) and connection string are also usually placed there.

Example for SQL Server:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="MyContext" connectionString="Data Source=mssqlserver2012;User ID=user;Password=password;Database=MyDatabase;" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

For Oracle the example can be the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>  
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    <section name="Devart.Data.Oracle.Entity" type="Devart.Data.Oracle.Entity.Configuration.OracleEntityProviderConfigurationSection, Devart.Data.Oracle.Entity, Version=8.5.602.6, Culture=neutral, PublicKeyToken=09af7300eec23701" />
  </configSections>
  <system.data>
    <DbProviderFactories>
      <remove invariant="Devart.Data.Oracle" />
      <add name="dotConnect for Oracle" invariant="Devart.Data.Oracle" description="Devart dotConnect for Oracle" type="Devart.Data.Oracle.OracleProviderFactory, Devart.Data.Oracle" />
    </DbProviderFactories>
  </system.data>
  <connectionStrings>
    <add name="MyOracleContext" connectionString="user id=user; password=password; server=ORCL1210; home=OraClient12Home1;" providerName="Devart.Data.Oracle" />
  </connectionStrings>  
  <entityFramework>
    <providers>
      <provider invariantName="Devart.Data.Oracle" type="Devart.Data.Oracle.Entity.OracleEntityProviderServices, Devart.Data.Oracle.Entity, Version=8.5.602.6, Culture=neutral, PublicKeyToken=09af7300eec23701" />
    </providers>
  </entityFramework>
  <Devart.Data.Oracle.Entity xmlns="http://devart.com/schemas/Devart.Data.Oracle.Entity/1.0">
    <DatabaseScript >
      <Table Tablespace="MYTABLESPACE" />
    </DatabaseScript>
  </Devart.Data.Oracle.Entity>
</configuration>

Additionally, Entity Framework 6 provider can be registered via a special DbConfigurationType attribute or via the special static method SetConfiguration of the DbConfiguration class. In the latter case you need to pass a specially configured DbConfiguration class descendant to this method.

Example of Entity Framework 6 provider registration via the DbConfigurationType attribute (can be used only for DbContext) for Devart dotConnect for Oracle:

  [DbConfigurationType(typeof(Devart.Data.Oracle.Entity.OracleEntityProviderServicesConfiguration))]
  public class MyContext : DbContext {
 
    // ...
  }

Example of Entity Framework 6 provider registration via the static SetConfiguration method of the DbConfiguration class (can be used both for DbContext and ObjectContext) for Devart dotConnect for Oracle:

  public class MyContext: ObjectContext {
 
    static MyContext() {
 
      DbConfiguration.SetConfiguration(new Devart.Data.Oracle.Entity.OracleEntityProviderServicesConfiguration());
    }
 
    // ...
  }

Good news is that Entity Framework Core does not require registering a provider factory for third-party ADO.NET providers. And this is the only good news.

What is not supported in Entity Framework Core:

  1. Entity Framework Core provider cannot be registered in the config file of the application. You must use code for the registration. And registering a provider via the DbConfigurationType attribute or DbConfiguration class is not supported any more.For Entity Framework Core you must override the OnConfiguring method of the DbContext, and specify the necessary provider in this method using a provider-specific extension method. Every Entity Framework Core provider has its own method name, and you can find it in the documentation of the corresponding provider. For SQL Server it is UseSqlServer(). This method is also used to specify the connection string:
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
     
      optionsBuilder.UseSqlServer(@"Data Source=mssqlserver2012;User ID=user;Password=password;Database=MyDatabase;");
    }
  2. In Entity Framework 6 you can store the connection string in the config file, and specify the same name for it as the corresponding DbContext class descendant name. For example:
    public class MyContext : DbContext {
     
       public MyContext() {
       }
     
       // ...
    }
      <connectionStrings>
        <add name="MyContext" connectionString="Data Source=mssqlserver2012;User ID=user;Password=password;Database=MyDatabase;" providerName="System.Data.SqlClient" />
      </connectionStrings>

    And this connection string is automatically used for this context. There is no such functionality in Entity Framework Core.

  3. In Entity Framework 6 you can specify the name of the connection string, stored in the config file, in the code. For example, in the following way:
    public class MyContext : DbContext {
     
      public MyContext()
        : base("name=MyConnectionStringName") {
      }
     
      // ...
    }
      <connectionStrings>
        <add name="MyConnectionStringName" connectionString="Data Source=mssqlserver2012;User ID=user;Password=password;Database=MyDatabase;" providerName="System.Data.SqlClient" />
      </connectionStrings>

    There is no such functionality in Entity Framework Core as well.

If your application uses only a minimal set of Entity Framework features, you are lucky, and you can stop here. Other ones should continue the migration process.

Additional Actions

Database Initializers

Database initializers no longer exist in Entity Framework Core. There are no more such strategies as CreateDatabaseIfNotExists, DropCreateDatabaseAlways, DropCreateDatabaseIfModelChanges, MigrateDatabaseToLatestVersion.

The following new methods of the Database class are offered as their replacement: EnsureDeleted() and EnsureCreated(). You need to create a context instance and execute the code at the beginning of the application execution. For example, the DropCreateDatabaseAlways functionality can be implemented in the following way:

context.Database.EnsureDeleted();
context.Database.EnsureCreated();

It seems simple and straightforward. The only thing you need to ensure is that this code is executed only once and it must be called before using the DbContext in any of the application threads.

Seeding Data

Entity Framework 6 supports custom database initializers. One could create a descendant of any of the standard database initializers and override the Seed() method for inserting initial data. Entity Framework Core does not have such functionality (Issue #629), and you need to create a context instance, execute code for deleting/creating a database or applying migrations, and after this add all the initial entities and call SaveChanges() at the beginning of application execution. Thus, the DropCreateDatabaseAlways functionality can be implemented in the following way:

context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.Category.Add(new Category() { Name = "Category1" });
context.Category.Add(new Category() { Name = "Category2" });
// ...
context.SaveChanges();

DbSet.Local

In Entity Framework 6, DbSet class has the Local property, which is very convenient in some cases. This property returns a collection of unmodified, added, and modified entities without accessing the database. There is no such property in Entity Framework Core (Issue #3915). We have added the Local() extensions method to the DbSet class that performs the same function.

namespace Microsoft.Data.Entity {
 
  public static class DbSetExtensions {
 
    public static IEnumerable<T> Local<T>(this DbSet<T> set)
      where T : class {
 
      var infrastructure = (Microsoft.Data.Entity.Infrastructure.IInfrastructure<IServiceProvider>)set;
 
      var context = (DbContext)infrastructure.Instance.GetService(typeof(DbContext));
 
      return context.ChangeTracker.Entries<T>()
        .Where(e => e.State == EntityState.Added || e.State == EntityState.Unchanged || e.State == EntityState.Modified)
        .Select(e => e.Entity);
    }
  }
}

Navigation Property Mapping

We used attribute mapping in our Entity Framework model, but we used it minimally, relying on the standard Code-First Conventions. In Entity Framework Core we have got a number of the following errors:

The navigation ‘PrimaryContact’ on entity type ‘CrmDemoCodeFirst.Company’ has not been added to the model, or ignored, or target entityType ignored.

Mostly the error occurred in cases when there are multiple associations between the same pair of classes. Entity Framework Core documentation says that “If there are multiple navigation properties defined between two types (i.e. more than one distinct pair of navigations that point to each other), then no relationships will be created by convention and you will need to manually configure them to identify how the navigation properties pair up.

The solution was to set the navigation property name on the other relationship side via the InverseProperty attribute. Here is the example:

    [InverseProperty(nameof(Order.Company))]
    public virtual ICollection<Order> Orders { get; set; }
 
    [InverseProperty(nameof(Order.ShipCompany))]
    public virtual ICollection<Order> ShippedOrders { get; set; }
 
    [InverseProperty(nameof(PersonContact.Company))]
    public virtual PersonContact PrimaryContact { get; set; }

Mapping Complex Primary Key

In Entity Framework 6 you can simply specify the [Key] attributes for two or more properties, and everything just worked.

  [Table("Order Details", Schema = "TEST")]
  public class OrderDetail {
 
    [Key]
    [Column(Order = 1)]
    public long OrderID { get; set; }
 
    [Key]
    [Column(Order = 2)]
    public long ProductID { get; set; }
 
    public double Price { get; set; }
    public double Quantity { get; set; }
 
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
  }

In Entity Framework Core it won’t work. According to Entity Framework Core documentation, “You can also use the Fluent API to configure multiple properties to be the key of an entity (known as a composite key). Composite keys can only be configured using the Fluent API – conventions will never setup a composite key and you can not use Data Annotations to configure one.

Thus, you cannot map a composite primary key using only data annotation attributes.

 protected override void OnModelCreating(ModelBuilder modelBuilder) {
 
      modelBuilder.Entity<OrderDetail>()
	    .HasKey(p => new { p.OrderID, p.ProductID }); 
 }

Mapping Complex Types

Entity Framework Core does not support complex types at all. Our model uses one complex type in two classes:

  [ComplexType]
  public class AddressType {
 
    [MaxLength(120)]
    public string AddressTitle { get; set; }
 
    [MaxLength(60)]
    public string Address { get; set; }
 
    [MaxLength(30)]
    public string City { get; set; }
 
    [MaxLength(20)]
    public string Region { get; set; }
 
    [MaxLength(15)]
    public string PostalCode { get; set; }
 
    [MaxLength(20)]
    public string Country { get; set; }
 
    [MaxLength(25)]
    public string Phone { get; set; }
 
    [MaxLength(25)]
    public string Fax { get; set; }
  }
 
  [Table("Company", Schema = "TEST")]
  public class Company {
 
    //...
 
    public AddressType Address { get; set; }
  }
 
  [Table("Person Contact", Schema = "TEST")]
  public class PersonContact {
 
    //...
 
    public AddressType Address { get; set; }
  }

This can be solved in several ways:

  1. Normalize the database (and sacrifice performance) by turning our complex type to a separate table.
    • pros: minimal application change, you just need to add a primary key property;
    • cons: lower performance of querying and modifying the data in the database, harder to work with data manually (for example, via SQL Server Management Studio), if necessary.
      [Table]
      public class AddressType {
     
        [Key]
        public long Id { get; set; } 
    
        [MaxLength(120)]
        public string AddressTitle { get; set; }
     
        [MaxLength(60)]
        public string Address { get; set; }
     
        [MaxLength(30)]
        public string City { get; set; }
     
        [MaxLength(20)]
        public string Region { get; set; }
     
        [MaxLength(15)]
        public string PostalCode { get; set; }
     
        [MaxLength(20)]
        public string Country { get; set; }
     
        [MaxLength(25)]
        public string Phone { get; set; }
     
        [MaxLength(25)]
        public string Fax { get; set; }
      }
  2. Leave the database denormalized.
    • pros: higher database operation speed;
    • cons: significant changes must be made to the application: you need to dismantle the complex type and rewrite all the LINQ queries, using it.
      [Table("Company", Schema = "TEST")]
      public class Company {
     
        //...
     
        [MaxLength(120)]
        public string AddressTitle { get; set; }
     
        [MaxLength(60)]
        public string Address { get; set; }
     
        [MaxLength(30)]
        public string City { get; set; }
     
        [MaxLength(20)]
        public string Region { get; set; }
     
        [MaxLength(15)]
        public string PostalCode { get; set; }
     
        [MaxLength(20)]
        public string Country { get; set; }
     
        [MaxLength(25)]
        public string Phone { get; set; }
     
        [MaxLength(25)]
        public string Fax { get; set; }
      }
     
      [Table("Person Contact", Schema = "TEST")]
      public class PersonContact {
     
        //...
     
        [MaxLength(120)]
        public string AddressTitle { get; set; }
     
        [MaxLength(60)]
        public string Address { get; set; }
     
        [MaxLength(30)]
        public string City { get; set; }
     
        [MaxLength(20)]
        public string Region { get; set; }
     
        [MaxLength(15)]
        public string PostalCode { get; set; }
     
        [MaxLength(20)]
        public string Country { get; set; }
     
        [MaxLength(25)]
        public string Phone { get; set; }
     
        [MaxLength(25)]
        public string Fax { get; set; }
      }

The End? This Is Not the End!

Finally, our application works! We have finished migration from Entity Framework 6 to Entity Framework Core.

But let’s be honest, we are just lucky that we haven’t used most part of Entity Framework 6 features, which is not supported in Entity Framework Core, and which we should have implement ourselves in case we had used it.

For example, the following features are not supported in Entity Framework Core:

  • Lazy Loading (also proxy classes and the Create() method of the DbSet class) (Issue #3797)
  • Explicit Loading – the Load() method for a navigation property (Issue #625)
  • Translating of GroupBy() to SQL (Issue #2341)
  • Translation of a LINQ query with a subquery to a single SQL query instead of multiple SQL queries, which can greatly impact performance (Issue #2320, Issue #202)
  • More complex mapping:
    • Using NOT NULL condition for discriminator column in Table-per-hierarchy (TPH) mapping, and using more than one discriminator column (Issue #4722)
    • Table-per-type (TPT) mapping (Issue #2266)
    • Table-per-concrete type (TPC) mapping (Issue #3170)
    • Using different inheritance kinds (TPH/TPC/TPT) in one hierarchy (there are no plans to implement it in Entity Framework Core)
    • Entity splitting (Issue #620)
    • Table splitting (Issue #619)
    • Multiple Entity Sets per Type (MEST) (there are no plans to implement it in Entity Framework Core)
  • Many-to-many relationship without an intermediate class, representing an intermediate table (Issue #1368)
  • Full-featured ChangeTracking API implementation (ChangeTracker and EntityEntry classes) (Issue #1198, Issue #1200, Issue #1201, Issue #1202, Issue #1203, Issue #2295)
  • The ObjectContext class and related infrastructure: ObjectSet, ObjectSet<TEntity>, IObjectSet<TEntity>, ObjectQuery, IObjectContextAdapter, ObjectStateManager, MetadataWorkspace, DbInterceptionContext. (There are no more such classes, and there are no plans to implement them; the DbContext and related infrastructure are offered to use instead)
  • Spatials – DbGeometry and DbGeography (Issue #1100)
  • Materializing results from a DbDataReader – the Translate() method (Issue #4675)
  • Ability to refresh set of entities with store values (Issue #2296)
  • Some Code-First mapping aspects:
    • Code-First fluent mapping, specified with descendants of the EntityTypeConfiguration and ComplexTypeConfiguration classes (Issue #2805)
    • Ability to generate a DDL script for database object creation, like ObjectContext.CreateDatabaseScript() (Issue #2943)
    • Allow Identity convention to be switched off (Issue #2369)
  • Working with stored functions and procedures:
    • Calling standard DBMS functions (for example, via the SqlFunctions class for SQL Server etc.) (Issue #2850)
    • Full-featured support of using methods and properties of .NET objects in LINQ to Entities queries, including the functionality of the DbFunctions class(Issue #2850, Issue #4255)
  • EntitySQL and all the related features (There are no plans to support EntitySQL in Entity Framework Core)

And since we used Code-First approach, we haven’t encountered problems with missing support for the following Database-First/Model-First features of Entity Framework 6:

  • XML mapping as edmx file or as a set of ssdl/msl/csdl files (There are no plans to support this feature in Entity Framework Core)
  • DefiningQuery when mapping an entity (Issue #3932)
  • CommandText when mapping a method
  • Calling of user-defined stored routines, including Table-valued functions (TVF), mapped to .NET methods (Issue #4319)
  • Mapping INSERT/UPDATE/DELETE entity operations to database stored procedures (Issue #245)

We don’t claim that the lists above are complete, but they can give a good understanding of a number of potential problems that one can encounter during migration to Entity Framework Core.

Conclusion

Currently Entity Framework Core (Entity Framework 7) can be suitable for new projects, when developers know and consider the ORM limitations and develop their projects having them in mind. The migration can also be relatively painless for projects that use only a minimal set of Entity Framework 6 features.

If you need a visual designer for Entity Framework models (for Database-First and Model-First approaches) and “Update Model From Database” feature, this can be solved by a third-party Entity Framework model designer tool, for example, Devart Entity Developer.

However, migration of a number of existing projects is not always advisable since in many cases it requires complete rewriting of a large parts of code, because the corresponding Entity Framework features are not implemented in Entity Framework Core. Probably, a better strategy in such cases is to wait for the subsequent Entity Framework Core releases till the necessary features will be implemented in it. Though this strategy can also be not always feasible since some Entity Framework features are not planned to implement in Entity Framework Core, and some other features have no deadlines defined.

RELATED ARTICLES

Whitepaper

Social

Topics

Products