Skip to content
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
22 of 25 tasks
ajcvickers opened this issue Jun 21, 2017 · 34 comments
Closed
22 of 25 tasks
Assignees
Labels
area-docs closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@ajcvickers
Copy link
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
Copy link
Member Author

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
Copy link

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

@ajcvickers
Copy link
Member Author

@DannyMeister Thanks.

@bricelam
Copy link
Contributor

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
Copy link
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
Copy link
Contributor

bricelam commented Jun 22, 2017

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

@bricelam
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

I'll take a crack at it.

@ErikEJ
Copy link
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
Copy link
Contributor

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

@ajcvickers
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Member Author

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

@pmiddleton
Copy link
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.

@divega
Copy link
Contributor

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
Copy link
Member

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

@arjenvrh
Copy link

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
Copy link
Member

@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
Copy link

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
Copy link
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
Copy link
Contributor

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 as completed Oct 10, 2017
vladislav-karamfilov referenced this issue in NikolayIT/ASP.NET-Core-Template Dec 23, 2017
- 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
Labels
area-docs closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests