New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write announcements for significant changes introduced in EF Core 2.0 #8923

Closed
ajcvickers opened this Issue Jun 21, 2017 · 34 comments

Comments

Projects
None yet
@ajcvickers
Member

ajcvickers commented Jun 21, 2017

This is an initial list of areas where an announcement might be useful. We will edit and update this list based on discussions within the team. We would also welcome feedback from anyone else who has used pre-release versions and has discovered something that would have been good for us to announce.

Discussed in triage and we will do one general announcement for impactful breaking changes and one general announcement for significant new features. Some very significant changes will have their own announcements.

Initial idea for process is:

  • People assigned below will write first draft as a comment on this issue.
  • We will then edit and when we're agreed it is ready we will check it off below.
  • Once everything is ready for a post, @divega will create the issue in Announcements and create the associated discussion issue.
@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Jun 22, 2017

Member

Initial drafts for my breaking change sections


EF Core 2.0 requires a 2.0 database provider

For EF Core 2.0 we have made many simplifications and improvements in the way database providers work. This means that 1.0.x and 1.1.x providers will not work with EF Core 2.0.

The SQL Server and SQLite providers are shipped by the EF team and 2.0 versions will be available as part of the 2.0 release. The open-source third party providers for SQL Compact, PostgreSQL, and MySQL are being updated for 2.0. For all other providers, please contact the provider writer.

Logging and Diagnostics events have changed

Note: these changes should not impact most application code.

The event IDs for messages sent to an ILogger have changed in 2.0. The event IDs are now unique across EF Core code. These messages now also follow the standard pattern for structured logging used by, for example, MVC.

Logger categories have also changed. There is now a well-known set of categories accessed through DbLoggerCategory.

DiagnosticSource events now use the same event ID names as the corresponding ILogger messages. The event payloads are all nominal types derived from EventData.

Event IDs, payload types, and categories are documented in the CoreEventId and the RelationalEventId classes.

EF Core relational metadata API changes

EF Core 2.0 will now build a different IModel for each different provider being used. This is usually transparent to the application. This has facilitated a simplification of lower-level metadata APIs such that any access to common relational metadata concepts is always made through a call to .Relational instead of .SqlServer, .Sqlite, etc. For example, 1.1.x code like this:

var tableName = context.Model.FindEntityType(typeof(User)).SqlServer().TableName;

Should now be written like this:

var tableName = context.Model.FindEntityType(typeof(User)).Relational().TableName;

Instead of using methods like ForSqlServerToTable, extension methods are now available to write conditional code based on the current provider in use. For example:

modelBuilder.Entity<User>().ToTable(
    Database.IsSqlServer() ? "SqlServerName" : "OtherName");

Note that this change only applies to APIs/metadata that is defined for all relational providers. The API and metadata remains the same when it is specific to only a single provider. For example, clustered indexes are specific to SQL Sever, so ForSqlServerIsClustered and .SqlServer().IsClustered() must still be used.

Don’t take control of the EF service provider

EF Core uses an internal IServiceProvider (D.I. container) for its internal implementation. Applications should allow EF Core to create and manage this provider except in special cases. Strongly consider removing any calls to UseInternalServiceProvider. If an application does need to call UseInternalServiceProvider, then please consider filing an issue so we can investigate other ways to handle your scenario.

Calling AddEntityFramework, AddEntityFrameworkSqlServer, etc. is not required by application code unless UseInternalServiceProvider is also called. Remove any existing calls to AddEntityFramework or AddEntityFrameworkSqlServer, etc. AddDbContext should still be used in the same way as before.

In-memory databases must be named

The global unnamed in-memory database has been removed and instead all in-memory databases must be named. For example:

optionsBuilder.UseInMemoryDatabase("MyDatabase");

This creates/uses a database with the name “MyDatabase”. If UseInMemoryDatabase is called again with the same name, then the same in-memory database will be used, allowing it to be shared by multiple context instances.

Read-only API changes

IsReadOnlyBeforeSave, IsReadOnlyAferSave, and IsStoreGeneratedAlways have been obsoleted and replaced with BeforeSaveBehavior and AfterSaveBehavior. These behaviors apply to any property (not only store-generated properties) and determine how the value of the property should be used when inserting into a database row (BeforeSaveBehavior) or when updating an existing database row (AfterSaveBehavior).

Properties marked as ValueGenerated.OnAddOrUpdate (e.g. for computed columns) will by default ignore any value currently set on the property. This means that a store-generated value will always be obtained regardless of whether any value has been set or modified on the tracked entity. This can be changed by setting a different Before\AfterSaveBehavior.

New ClientSetNull delete behavior

In previous releases, DeleteBehavior.Restrict had a behavior for entities tracked by the context that more closed matched SetNull semantics. In EF Core 2.0, a new ClientSetNull behavior has been introduced as the default for optional relationships. This behavior has SetNull semantics for tracked entities and Restrict behavior for databases created using EF Core. In our experience, these are the most expected/useful behaviors for tracked entities and the database. DeleteBehavior.Restrict is now honored for tracked entities when set for optional relationships.

Member

ajcvickers commented Jun 22, 2017

Initial drafts for my breaking change sections


EF Core 2.0 requires a 2.0 database provider

For EF Core 2.0 we have made many simplifications and improvements in the way database providers work. This means that 1.0.x and 1.1.x providers will not work with EF Core 2.0.

The SQL Server and SQLite providers are shipped by the EF team and 2.0 versions will be available as part of the 2.0 release. The open-source third party providers for SQL Compact, PostgreSQL, and MySQL are being updated for 2.0. For all other providers, please contact the provider writer.

Logging and Diagnostics events have changed

Note: these changes should not impact most application code.

The event IDs for messages sent to an ILogger have changed in 2.0. The event IDs are now unique across EF Core code. These messages now also follow the standard pattern for structured logging used by, for example, MVC.

Logger categories have also changed. There is now a well-known set of categories accessed through DbLoggerCategory.

DiagnosticSource events now use the same event ID names as the corresponding ILogger messages. The event payloads are all nominal types derived from EventData.

Event IDs, payload types, and categories are documented in the CoreEventId and the RelationalEventId classes.

EF Core relational metadata API changes

EF Core 2.0 will now build a different IModel for each different provider being used. This is usually transparent to the application. This has facilitated a simplification of lower-level metadata APIs such that any access to common relational metadata concepts is always made through a call to .Relational instead of .SqlServer, .Sqlite, etc. For example, 1.1.x code like this:

var tableName = context.Model.FindEntityType(typeof(User)).SqlServer().TableName;

Should now be written like this:

var tableName = context.Model.FindEntityType(typeof(User)).Relational().TableName;

Instead of using methods like ForSqlServerToTable, extension methods are now available to write conditional code based on the current provider in use. For example:

modelBuilder.Entity<User>().ToTable(
    Database.IsSqlServer() ? "SqlServerName" : "OtherName");

Note that this change only applies to APIs/metadata that is defined for all relational providers. The API and metadata remains the same when it is specific to only a single provider. For example, clustered indexes are specific to SQL Sever, so ForSqlServerIsClustered and .SqlServer().IsClustered() must still be used.

Don’t take control of the EF service provider

EF Core uses an internal IServiceProvider (D.I. container) for its internal implementation. Applications should allow EF Core to create and manage this provider except in special cases. Strongly consider removing any calls to UseInternalServiceProvider. If an application does need to call UseInternalServiceProvider, then please consider filing an issue so we can investigate other ways to handle your scenario.

Calling AddEntityFramework, AddEntityFrameworkSqlServer, etc. is not required by application code unless UseInternalServiceProvider is also called. Remove any existing calls to AddEntityFramework or AddEntityFrameworkSqlServer, etc. AddDbContext should still be used in the same way as before.

In-memory databases must be named

The global unnamed in-memory database has been removed and instead all in-memory databases must be named. For example:

optionsBuilder.UseInMemoryDatabase("MyDatabase");

This creates/uses a database with the name “MyDatabase”. If UseInMemoryDatabase is called again with the same name, then the same in-memory database will be used, allowing it to be shared by multiple context instances.

Read-only API changes

IsReadOnlyBeforeSave, IsReadOnlyAferSave, and IsStoreGeneratedAlways have been obsoleted and replaced with BeforeSaveBehavior and AfterSaveBehavior. These behaviors apply to any property (not only store-generated properties) and determine how the value of the property should be used when inserting into a database row (BeforeSaveBehavior) or when updating an existing database row (AfterSaveBehavior).

Properties marked as ValueGenerated.OnAddOrUpdate (e.g. for computed columns) will by default ignore any value currently set on the property. This means that a store-generated value will always be obtained regardless of whether any value has been set or modified on the tracked entity. This can be changed by setting a different Before\AfterSaveBehavior.

New ClientSetNull delete behavior

In previous releases, DeleteBehavior.Restrict had a behavior for entities tracked by the context that more closed matched SetNull semantics. In EF Core 2.0, a new ClientSetNull behavior has been introduced as the default for optional relationships. This behavior has SetNull semantics for tracked entities and Restrict behavior for databases created using EF Core. In our experience, these are the most expected/useful behaviors for tracked entities and the database. DeleteBehavior.Restrict is now honored for tracked entities when set for optional relationships.

@DannyMeister

This comment has been minimized.

Show comment
Hide comment
@DannyMeister

DannyMeister Jun 22, 2017

You'll want to fix the spelling of AfterSaveBhevaior in the Read-only Api Changes section.

DannyMeister commented Jun 22, 2017

You'll want to fix the spelling of AfterSaveBhevaior in the Read-only Api Changes section.

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers
Member

ajcvickers commented Jun 22, 2017

@DannyMeister Thanks.

@bricelam

This comment has been minimized.

Show comment
Hide comment
@bricelam

bricelam Jun 22, 2017

Member

Provider design-time packages removed

The Microsoft.EntityFrameworkCore.Relational.Design package has been removed. It's contents were consolidated into Microsoft.EntityFrameworkCore.Relational and Microsoft.EntityFrameworkCore.Design.

This propagates into the provider design-time packages. Those packages (Microsoft.EntityFrameworkCore.Sqlite.Design, Microsoft.EntityFrameworkCore.SqlServer.Design, etc.) were removed and their contents consolidated into the main provider packages.

To enable Scaffold-DbContext or dotnet ef dbcontext scaffold in EF Core 2.0, you only need to reference the single provider package:

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
                  Version="2.0.0-preview2-final" />

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design"
                  Version="2.0.0-preview2-final"
                  PrivateAssets="All" />

<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
                  Version="2.0.0-preview2-final"
                  PrivateAssets="All" />

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet"
                        Version="2.0.0-preview2-final" />
Member

bricelam commented Jun 22, 2017

Provider design-time packages removed

The Microsoft.EntityFrameworkCore.Relational.Design package has been removed. It's contents were consolidated into Microsoft.EntityFrameworkCore.Relational and Microsoft.EntityFrameworkCore.Design.

This propagates into the provider design-time packages. Those packages (Microsoft.EntityFrameworkCore.Sqlite.Design, Microsoft.EntityFrameworkCore.SqlServer.Design, etc.) were removed and their contents consolidated into the main provider packages.

To enable Scaffold-DbContext or dotnet ef dbcontext scaffold in EF Core 2.0, you only need to reference the single provider package:

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
                  Version="2.0.0-preview2-final" />

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design"
                  Version="2.0.0-preview2-final"
                  PrivateAssets="All" />

<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
                  Version="2.0.0-preview2-final"
                  PrivateAssets="All" />

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet"
                        Version="2.0.0-preview2-final" />
@ErikEJ

This comment has been minimized.

Show comment
Hide comment
@ErikEJ

ErikEJ Jun 22, 2017

Contributor

Brice, I find it slightly confusing that you have mentioned a non-existing package in the sample csproj file, despite the coloring

Contributor

ErikEJ commented Jun 22, 2017

Brice, I find it slightly confusing that you have mentioned a non-existing package in the sample csproj file, despite the coloring

@bricelam

This comment has been minimized.

Show comment
Hide comment
@bricelam

bricelam Jun 22, 2017

Member

Agreed. I was hoping it would strike through the lines. Updated.

Member

bricelam commented Jun 22, 2017

Agreed. I was hoping it would strike through the lines. Updated.

@bricelam

This comment has been minimized.

Show comment
Hide comment
@bricelam

bricelam Jun 22, 2017

Member

EF Core design-time DbContext discovery changes

There have been some changes made in 2.0 to the way EF Core discovers and creates your DbContext at design-time.

New way of getting application services

The recommended pattern for ASP.NET Core web applications has been updated for 2.0 in a way that broke EF Core's design-time logic. Previously at design-time, EF Core would try to invoke Startup.ConfigureServices directly in order to access the application's service provider. In ASP.NET Core 2.0, Configuration is initialized outside of the Startup class. Applications using EF Core typically access their connection string from Configuration, so Startup by itself is no longer sufficient. If you upgrade an ASP.NET Core 1.x application, you may receive the following error when using the EF Core tools.

No parameterless constructor was found on 'ApplicationContext'. Either add a parameterless constructor to 'ApplicationContext' or add an implementation of 'IDesignTimeDbContextFactory<ApplicationContext>' in the same assembly as 'ApplicationContext'

A new design-time hook has been added in ASP.NET Core 2.0. The static Program.BuildWebHost method enables EF Core to access the application's service provider at design time. If you are upgrading an ASP.NET Core 1.x application, you will need to update you Program class to resemble the following.

public class Program
{
    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        host.Run();
    }

    // Tools will use this to get application services
    public static IWebHost BuildWebHost(string[] args) =>
        new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();
}

IDbContextFactory renamed

In order to support diverse application patterns and give users more control over how their DbContext is used at design time, we have, in the past, provided the IDbContextFactory<TContext> interface. At design-time, the EF Core tools will discover implementations of this interface in your project and use it to create DbContext objects.

This interface had a very general name which mislead some users to try re-using it for other DbContext-creating scenarios. They were caught off guard when the EF Tools then tried to use their implementation at design-time and caused commands like Update-Database or dotnet ef database update to fail.

In order to communicate the strong design-time semantics of this interface, we have renamed it to IDesignTimeDbContextFactory<TContext>.

DbContextFactoryOptions removed

Because of the ASP.NET Core 2.0 changes described above, we found that DbContextFactoryOptions was no longer needed on the new IDesignTimeDbContextFactory<TContext> interface. Here are the alternatives you should be using instead.

DbContextFactoryOptions Alternative
ApplicationBasePath AppContext.BaseDirectory
ContentRootPath Directory.GetCurrentDirectory()
EnvironmentName Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
Member

bricelam commented Jun 22, 2017

EF Core design-time DbContext discovery changes

There have been some changes made in 2.0 to the way EF Core discovers and creates your DbContext at design-time.

New way of getting application services

The recommended pattern for ASP.NET Core web applications has been updated for 2.0 in a way that broke EF Core's design-time logic. Previously at design-time, EF Core would try to invoke Startup.ConfigureServices directly in order to access the application's service provider. In ASP.NET Core 2.0, Configuration is initialized outside of the Startup class. Applications using EF Core typically access their connection string from Configuration, so Startup by itself is no longer sufficient. If you upgrade an ASP.NET Core 1.x application, you may receive the following error when using the EF Core tools.

No parameterless constructor was found on 'ApplicationContext'. Either add a parameterless constructor to 'ApplicationContext' or add an implementation of 'IDesignTimeDbContextFactory<ApplicationContext>' in the same assembly as 'ApplicationContext'

A new design-time hook has been added in ASP.NET Core 2.0. The static Program.BuildWebHost method enables EF Core to access the application's service provider at design time. If you are upgrading an ASP.NET Core 1.x application, you will need to update you Program class to resemble the following.

public class Program
{
    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        host.Run();
    }

    // Tools will use this to get application services
    public static IWebHost BuildWebHost(string[] args) =>
        new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();
}

IDbContextFactory renamed

In order to support diverse application patterns and give users more control over how their DbContext is used at design time, we have, in the past, provided the IDbContextFactory<TContext> interface. At design-time, the EF Core tools will discover implementations of this interface in your project and use it to create DbContext objects.

This interface had a very general name which mislead some users to try re-using it for other DbContext-creating scenarios. They were caught off guard when the EF Tools then tried to use their implementation at design-time and caused commands like Update-Database or dotnet ef database update to fail.

In order to communicate the strong design-time semantics of this interface, we have renamed it to IDesignTimeDbContextFactory<TContext>.

DbContextFactoryOptions removed

Because of the ASP.NET Core 2.0 changes described above, we found that DbContextFactoryOptions was no longer needed on the new IDesignTimeDbContextFactory<TContext> interface. Here are the alternatives you should be using instead.

DbContextFactoryOptions Alternative
ApplicationBasePath AppContext.BaseDirectory
ContentRootPath Directory.GetCurrentDirectory()
EnvironmentName Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
@anpete

This comment has been minimized.

Show comment
Hide comment
@anpete

anpete Jun 22, 2017

Model-level query filters

EF Core 2.0 includes a new feature we call Model-level query filters. This feature allows LINQ query predicates (a boolean expression typically passed to the LINQ Where query operator) to be defined directly on Entity Types in the metadata model (usually in OnModelCreating). Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references. Some common applications of this feature are:

  • Soft delete - An Entity Types defines an IsDeleted property.
  • Multi-tenancy - An Entity Type defines a TenantId property.

Here is a simple example demonstrating the feature for the two scenarios listed above:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId {get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted && p.TenantId == this.TenantId );
    }
}

We define a model-level filter that implements multi-tenancy and soft-delete for instances of the Post Entity Type. Note the use of a DbContext instance level property: TenantId. Model-level filters will use the value from the correct context instance. I.e. the one that is executing the query.

Filters may be disabled for individual LINQ queries using the IgnoreQueryFilters() operator.

Limitations

  • Navigation references are not allowed. This feature may be added based on feedback.
  • Filters can only be defined on the root Entity Type of a hierarchy.

anpete commented Jun 22, 2017

Model-level query filters

EF Core 2.0 includes a new feature we call Model-level query filters. This feature allows LINQ query predicates (a boolean expression typically passed to the LINQ Where query operator) to be defined directly on Entity Types in the metadata model (usually in OnModelCreating). Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references. Some common applications of this feature are:

  • Soft delete - An Entity Types defines an IsDeleted property.
  • Multi-tenancy - An Entity Type defines a TenantId property.

Here is a simple example demonstrating the feature for the two scenarios listed above:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId {get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted && p.TenantId == this.TenantId );
    }
}

We define a model-level filter that implements multi-tenancy and soft-delete for instances of the Post Entity Type. Note the use of a DbContext instance level property: TenantId. Model-level filters will use the value from the correct context instance. I.e. the one that is executing the query.

Filters may be disabled for individual LINQ queries using the IgnoreQueryFilters() operator.

Limitations

  • Navigation references are not allowed. This feature may be added based on feedback.
  • Filters can only be defined on the root Entity Type of a hierarchy.
@anpete

This comment has been minimized.

Show comment
Hide comment
@anpete

anpete Jun 22, 2017

DbContext pooling and explicit compiled queries

In EF Core 2.0 we added two new opt-in performance features designed to offer benefits in high-scale scenarios.

DbContext pooling

The basic pattern for using EF Core in an ASP.NET Core application usually involves registering a custom DbContext type into the dependency injection system and later obtaining instances of that type through constructor parameters in controllers. This means a new instance of the DbContext is created for each requests.

In version 2.0 we are introducing a new way to register custom DbContext types in dependency injection which transparently introduces a pool of reusable DbContext instances. To use DbContext pooling, use the AddDbContextPool instead of AddDbContext during service registration:

services.AddDbContextPool<BloggingContext>(options => options.UseSqlServer(connectionString));

If this method is used, at the time a DbContext instance is requested by a controller we will first check if there is an instance available in the pool. Once the request processing finalizes, any state on the instance is reset and the instance is itself returned to the pool.

This is conceptually similar to how connection pooling operates in ADO.NET providers and has the advantage of saving some of the cost of initialization of DbContext instance.

The new method introduces a few limitations on what can be done in the OnConfiguring() method of the DbContext but it can be adopted by many ASP.NET Core applications to obtain a performance boost.

Explicit compiled queries

Manual or explicitly compiled query APIs have been available in previous versions of EF and also in LINQ to SQL to allow applications to cache the translation of queries so that they can be computed only once and executed many times.

Although in general EF Core can automatically compile and cache queries based on a hashed representation of the query expressions, this mechanism can be used to obtain a small performance gain by bypassing the computation of the hash and the cache lookup, allowing the application to use an already compiled query through the invocation of a delegate.

// Create an explicit compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}

anpete commented Jun 22, 2017

DbContext pooling and explicit compiled queries

In EF Core 2.0 we added two new opt-in performance features designed to offer benefits in high-scale scenarios.

DbContext pooling

The basic pattern for using EF Core in an ASP.NET Core application usually involves registering a custom DbContext type into the dependency injection system and later obtaining instances of that type through constructor parameters in controllers. This means a new instance of the DbContext is created for each requests.

In version 2.0 we are introducing a new way to register custom DbContext types in dependency injection which transparently introduces a pool of reusable DbContext instances. To use DbContext pooling, use the AddDbContextPool instead of AddDbContext during service registration:

services.AddDbContextPool<BloggingContext>(options => options.UseSqlServer(connectionString));

If this method is used, at the time a DbContext instance is requested by a controller we will first check if there is an instance available in the pool. Once the request processing finalizes, any state on the instance is reset and the instance is itself returned to the pool.

This is conceptually similar to how connection pooling operates in ADO.NET providers and has the advantage of saving some of the cost of initialization of DbContext instance.

The new method introduces a few limitations on what can be done in the OnConfiguring() method of the DbContext but it can be adopted by many ASP.NET Core applications to obtain a performance boost.

Explicit compiled queries

Manual or explicitly compiled query APIs have been available in previous versions of EF and also in LINQ to SQL to allow applications to cache the translation of queries so that they can be computed only once and executed many times.

Although in general EF Core can automatically compile and cache queries based on a hashed representation of the query expressions, this mechanism can be used to obtain a small performance gain by bypassing the computation of the hash and the cache lookup, allowing the application to use an already compiled query through the invocation of a delegate.

// Create an explicit compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}
@anpete

This comment has been minimized.

Show comment
Hide comment
@anpete

anpete Jun 22, 2017

String interpolation in FromSql and ExecuteSqlCommand

C# 6 introduced String Interpolation, a feature that allows C# expressions to be directly embedded in string literals, providing a nice way of building strings at runtime. In EF Core 2.0 we added special support for interpolated strings to our two primary APIs that accept raw SQL strings: FromSql and ExecuteSqlCommand. This new support allows C# string interpolation to be used in a 'safe' manner. I.e. in a way that protects against common SQL injection mistakes that can occur when dynamically constructing SQL at runtime.

Here is an example:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>().FromSql($@"SELECT * FROM ""Customers"" WHERE ""City"" = {city} AND ""ContactTitle"" = {contactTitle}").ToArray();
}

In this example, there are two variables embedded in the SQL format string. EF Core will produce the following SQL:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT * FROM ""Customers"" WHERE ""City"" = @p0 AND ""ContactTitle"" = @p1

anpete commented Jun 22, 2017

String interpolation in FromSql and ExecuteSqlCommand

C# 6 introduced String Interpolation, a feature that allows C# expressions to be directly embedded in string literals, providing a nice way of building strings at runtime. In EF Core 2.0 we added special support for interpolated strings to our two primary APIs that accept raw SQL strings: FromSql and ExecuteSqlCommand. This new support allows C# string interpolation to be used in a 'safe' manner. I.e. in a way that protects against common SQL injection mistakes that can occur when dynamically constructing SQL at runtime.

Here is an example:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>().FromSql($@"SELECT * FROM ""Customers"" WHERE ""City"" = {city} AND ""ContactTitle"" = {contactTitle}").ToArray();
}

In this example, there are two variables embedded in the SQL format string. EF Core will produce the following SQL:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT * FROM ""Customers"" WHERE ""City"" = @p0 AND ""ContactTitle"" = @p1
@anpete

This comment has been minimized.

Show comment
Hide comment
@anpete

anpete Jun 22, 2017

@pmiddleton If you are interested in writing the announcement post for DbFunctions, you can write it here. Up to you, but I thought I would give you the option 😄

anpete commented Jun 22, 2017

@pmiddleton If you are interested in writing the announcement post for DbFunctions, you can write it here. Up to you, but I thought I would give you the option 😄

@pmiddleton

This comment has been minimized.

Show comment
Hide comment
@pmiddleton

pmiddleton Jun 23, 2017

Contributor

I'll take a crack at it.

Contributor

pmiddleton commented Jun 23, 2017

I'll take a crack at it.

@ErikEJ

This comment has been minimized.

Show comment
Hide comment
@ErikEJ

ErikEJ Jun 23, 2017

Contributor

How about: EF Core 2.0 no longer works with .NET 4.5.1, but requires 4.6.1 ?

Contributor

ErikEJ commented Jun 23, 2017

How about: EF Core 2.0 no longer works with .NET 4.5.1, but requires 4.6.1 ?

@bricelam

This comment has been minimized.

Show comment
Hide comment
@bricelam

bricelam Jun 23, 2017

Member

(Added a comment about .NET Framework 4.6.1 to the .NET Standed 2.0 item.)

Member

bricelam commented Jun 23, 2017

(Added a comment about .NET Framework 4.6.1 to the .NET Standed 2.0 item.)

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Jun 23, 2017

Member

Attach can track a graph of new and existing entities

EF Core supports automatic generation of key values through a variety of mechanisms. When using this feature, a value is generated if the key property is the CLR default--usually zero or null. This means that a graph of entities can be passed to DbContext.Attach or DbSet.Attach and EF Core will mark those entities that have a key already set as Unchanged while those entities that do not have a key set will be marked as Added. This makes it easy to attach a graph of mixed new and existing entities when using generated keys. DbContext.Update and DbSet.Update work in the same way, except that entities with a key set are marked as Modified instead of Unchanged.

Member

ajcvickers commented Jun 23, 2017

Attach can track a graph of new and existing entities

EF Core supports automatic generation of key values through a variety of mechanisms. When using this feature, a value is generated if the key property is the CLR default--usually zero or null. This means that a graph of entities can be passed to DbContext.Attach or DbSet.Attach and EF Core will mark those entities that have a key already set as Unchanged while those entities that do not have a key set will be marked as Added. This makes it easy to attach a graph of mixed new and existing entities when using generated keys. DbContext.Update and DbSet.Update work in the same way, except that entities with a key set are marked as Modified instead of Unchanged.

@anpete

This comment has been minimized.

Show comment
Hide comment
@anpete

anpete Jun 23, 2017

Warning as error for ignored Includes

A very common issue we see when looking at user LINQ queries is the use of Include where it is unnecessary. The typical pattern usually looks something like this:

context.Orders.Include(o => o.Product).Where(o => o.Product.Name == "Baked Beans").Select(o =>o.ProductId).ToList();

One might assume that the Include operation here is required because of the reference to the Product navigation property in the Where and Select operations. However, in EF, these two things are orthogonal: Include controls which navigation properties are loaded in entities returned in the final results, and our LINQ translator can directly translate expressions involving navigation properties.

Previously we would generate a warning when we encountered queries like this, but in EF Core 2.0 we are changing the default WarningBehavior for this event to Throw. We are doing this primarily in the hopes of promoting a better understanding of the role that the Include operator plays in EF Core LINQ queries.

That said, it is easy to configure EF Core to use the previous, non-throwing behavior via our flexible 'warnings-as-errors' configuration system.

// In OnConfiguring or AddDbContext during start-up
optionsBuilder.ConfigureWarnings(w => w.Log(CoreEventId.IncludeIgnoredWarning)); // Log but don't throw

anpete commented Jun 23, 2017

Warning as error for ignored Includes

A very common issue we see when looking at user LINQ queries is the use of Include where it is unnecessary. The typical pattern usually looks something like this:

context.Orders.Include(o => o.Product).Where(o => o.Product.Name == "Baked Beans").Select(o =>o.ProductId).ToList();

One might assume that the Include operation here is required because of the reference to the Product navigation property in the Where and Select operations. However, in EF, these two things are orthogonal: Include controls which navigation properties are loaded in entities returned in the final results, and our LINQ translator can directly translate expressions involving navigation properties.

Previously we would generate a warning when we encountered queries like this, but in EF Core 2.0 we are changing the default WarningBehavior for this event to Throw. We are doing this primarily in the hopes of promoting a better understanding of the role that the Include operator plays in EF Core LINQ queries.

That said, it is easy to configure EF Core to use the previous, non-throwing behavior via our flexible 'warnings-as-errors' configuration system.

// In OnConfiguring or AddDbContext during start-up
optionsBuilder.ConfigureWarnings(w => w.Log(CoreEventId.IncludeIgnoredWarning)); // Log but don't throw
@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Jun 26, 2017

Member

@AndriySvyryd Can you work on your sections today--then we can hand off to @divega.

Member

ajcvickers commented Jun 26, 2017

@AndriySvyryd Can you work on your sections today--then we can hand off to @divega.

@pmiddleton

This comment has been minimized.

Show comment
Hide comment
@pmiddleton

pmiddleton Jun 26, 2017

Contributor

I didn't have time this weekend to work my section. I will tonight, but if there is a close deadline someone else can take it.

Contributor

pmiddleton commented Jun 26, 2017

I didn't have time this weekend to work my section. I will tonight, but if there is a close deadline someone else can take it.

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Jun 30, 2017

Member

@aghe I responded with the same information to your comment in the blog post as well. There is now an announcement post with more details, hopefully it will be clearer. If you have any more feedback on this, I encourage you to post to the announcement's discussion thread. Thanks.

Member

divega commented Jun 30, 2017

@aghe I responded with the same information to your comment in the blog post as well. There is now an announcement post with more details, hopefully it will be clearer. If you have any more feedback on this, I encourage you to post to the announcement's discussion thread. Thanks.

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Jul 4, 2017

Member

@AndriySvyryd re the owned types example code, is it supposed to just work as is in preview 2? I actually get an error:

System.InvalidOperationException occurred
HResult=0x80131509
Message=Cannot use table 'Orders' for entity type 'Order.OrderDetails#OrderDetails.BillingAddress#Address' since it is being used for entity type 'Order' and there is no relationship between the primary key {'OrderDetailsOrderId'} and the primary key {'Id'}.
Source=
StackTrace:
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedTableCompatibility(IEntityType newEntityType, List`1 otherMappedTypes, String tableName)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.ValidateSharedTableCompatibility(IEntityType newEntityType, List`1 otherMappedTypes, String tableName)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedTableCompatibility(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.b__0(Object k)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.b__7_1(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass17_0.b__0(ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at ConsoleApp2.Program.Main(String[] args) in C:\Users\divega\Downloads\Backup\SelectTest\SelectTest\ConsoleApp2\Program.cs:line 11

Member

divega commented Jul 4, 2017

@AndriySvyryd re the owned types example code, is it supposed to just work as is in preview 2? I actually get an error:

System.InvalidOperationException occurred
HResult=0x80131509
Message=Cannot use table 'Orders' for entity type 'Order.OrderDetails#OrderDetails.BillingAddress#Address' since it is being used for entity type 'Order' and there is no relationship between the primary key {'OrderDetailsOrderId'} and the primary key {'Id'}.
Source=
StackTrace:
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedTableCompatibility(IEntityType newEntityType, List`1 otherMappedTypes, String tableName)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.ValidateSharedTableCompatibility(IEntityType newEntityType, List`1 otherMappedTypes, String tableName)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.ValidateSharedTableCompatibility(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.b__0(Object k)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.b__7_1(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass17_0.b__0(ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at ConsoleApp2.Program.Main(String[] args) in C:\Users\divega\Downloads\Backup\SelectTest\SelectTest\ConsoleApp2\Program.cs:line 11

@AndriySvyryd

This comment has been minimized.

Show comment
Hide comment
@AndriySvyryd

AndriySvyryd Jul 4, 2017

Member

@divega This was fixed post preview2, as a workaround you can rename the navigations to change the order. I can send you the fixed sample.

Member

AndriySvyryd commented Jul 4, 2017

@divega This was fixed post preview2, as a workaround you can rename the navigations to change the order. I can send you the fixed sample.

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Jul 5, 2017

Member

@AndriySvyryd Thanks. Glad to hear. It would be great if you can either give me a fixed example or more information about the workaround (I tried what I understood by "rename the navigation to change the order", but didn't help). I would like to update the preview2 announcement blog post with code that works, or otherwise add a disclaimer that this won't work correctly until RTM.

Member

divega commented Jul 5, 2017

@AndriySvyryd Thanks. Glad to hear. It would be great if you can either give me a fixed example or more information about the workaround (I tried what I understood by "rename the navigation to change the order", but didn't help). I would like to update the preview2 announcement blog post with code that works, or otherwise add a disclaimer that this won't work correctly until RTM.

@AndriySvyryd

This comment has been minimized.

Show comment
Hide comment
@AndriySvyryd

AndriySvyryd Jul 5, 2017

Member

@divega Renaming Address to StreetAddress works. Updated the comment above.

Member

AndriySvyryd commented Jul 5, 2017

@divega Renaming Address to StreetAddress works. Updated the comment above.

@arjenvrh

This comment has been minimized.

Show comment
Hide comment
@arjenvrh

arjenvrh Jul 6, 2017

@AndriySvyryd I tried out the Owner Types in Preview2 with the following classes:

public class ParentClass {
    [Required]
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid Id { get; set; }
    
    public virtual OwnedClass Ref { get; set; }
}

public class OwnedClass
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
}

and the following Mapping:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
    });

Although it is stated above that when querying the owner (ParentClass) the owned types will be included by default, in my tests I have to explicitly include the "Ref" property. In this case loading works, however I ran into a few problems:

  • When I load a ParentClass with an OwnedClass and set the "Ref" property to null, saving the ParentClass fails.
  • When I try to delete a ParentClass item it throws an error of the ChangeTracker with a KeyNotFoundException.

Are these issues known in Preview 2?

arjenvrh commented Jul 6, 2017

@AndriySvyryd I tried out the Owner Types in Preview2 with the following classes:

public class ParentClass {
    [Required]
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Guid Id { get; set; }
    
    public virtual OwnedClass Ref { get; set; }
}

public class OwnedClass
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
}

and the following Mapping:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
    });

Although it is stated above that when querying the owner (ParentClass) the owned types will be included by default, in my tests I have to explicitly include the "Ref" property. In this case loading works, however I ran into a few problems:

  • When I load a ParentClass with an OwnedClass and set the "Ref" property to null, saving the ParentClass fails.
  • When I try to delete a ParentClass item it throws an error of the ChangeTracker with a KeyNotFoundException.

Are these issues known in Preview 2?

@AndriySvyryd

This comment has been minimized.

Show comment
Hide comment
@AndriySvyryd

AndriySvyryd Jul 6, 2017

Member

@arjenvrh The navigations are not included automatically in preview2, however the other issue was not known, I filed it separately as #9086

Member

AndriySvyryd commented Jul 6, 2017

@arjenvrh The navigations are not included automatically in preview2, however the other issue was not known, I filed it separately as #9086

@ajcvickers ajcvickers modified the milestones: Backlog, 2.0.0 Jul 6, 2017

@arjenvrh

This comment has been minimized.

Show comment
Hide comment
@arjenvrh

arjenvrh Jul 7, 2017

@AndriySvyryd Thanks, that was quick! Another question regarding owned types: is it possible to define a custom index over the columns in the owned type?

Something like this:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
    });
				
modelBuilder.Entity<ParentClass>()
    .HasIndex(s => new { s.Ref.Field1, s.Ref.Field2 }).HasName("IDX_CustomName");

arjenvrh commented Jul 7, 2017

@AndriySvyryd Thanks, that was quick! Another question regarding owned types: is it possible to define a custom index over the columns in the owned type?

Something like this:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
    });
				
modelBuilder.Entity<ParentClass>()
    .HasIndex(s => new { s.Ref.Field1, s.Ref.Field2 }).HasName("IDX_CustomName");
@AndriySvyryd

This comment has been minimized.

Show comment
Hide comment
@AndriySvyryd

AndriySvyryd Jul 7, 2017

Member

@arjenvrh There's an issue in preview2 with the generic overload #9106. But you can just create the index in the owned type configuration:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
        cb.HasIndex("Field1", "Field2").HasName("IDX_CustomName");
    });

However we don't support creating indexes that span across types, but if that's important for your scenario file it as a separate issue.

Member

AndriySvyryd commented Jul 7, 2017

@arjenvrh There's an issue in preview2 with the generic overload #9106. But you can just create the index in the owned type configuration:

modelBuilder.Entity<ParentClass>()
    .OwnsOne<OwnedClass>(s => s.Ref, cb =>
    {
        cb.Property(c => c.Field1).HasColumnName("OwnedField1");
        cb.Property(c => c.Field2).HasColumnName("OwnedField2");
        cb.HasIndex("Field1", "Field2").HasName("IDX_CustomName");
    });

However we don't support creating indexes that span across types, but if that's important for your scenario file it as a separate issue.

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Oct 10, 2017

Member

This was all added to the documentation page at https://docs.microsoft.com/en-us/ef/core/what-is-new/index on time for 2.0. Only created announcements for the specially controversial decisions in which we needed to actively seek for feedback the most.

Member

divega commented Oct 10, 2017

This was all added to the documentation page at https://docs.microsoft.com/en-us/ef/core/what-is-new/index on time for 2.0. Only created announcements for the specially controversial decisions in which we needed to actively seek for feedback the most.

@divega divega closed this Oct 10, 2017

vladislav-karamfilov referenced this issue in NikolayIT/ASP.NET-MVC-Template Dec 23, 2017

[ASP.NET Core with Angular] NuGet packages updated:
- AutoMapper 6.2.2
- Microsoft.EntityFrameworkCore.SqlServer.Design 1.1.5
- System.ComponentModel.Annotations 4.4.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment