Scaffold-DbContext generates incorrect nav property on foreign key with filtered unique index

Open
#11,298 4 comments 8 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
42/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
csharp, sql
Domain
databases

Research direction

Reproduce the issue from the supplied SQL Server schema and Scaffold-DbContext command, then inspect the generated Person and DbContext output. Trace how the filtered unique index is interpreted when configuring the PersonRole relationship. Done means the generated parent navigation is a collection while the index filter remains represented in the model configuration.

Written by the indexing model from the issue text.

Description

area-scaffolding blocked

I've run into an issue using the Scaffold-DbContext command for the Package Manager Console in Visual Studio to reverse engineer a database on a SQL Server 2016 instance. It seems that command misinterprets one-to-many foreign key relationships as one-to-one when there is a filtered unique index on the foreign key column(s) in the child table. The navigation property on the entity type generated for the parent table should be a collection of child entities but it is instead scaffolded as a single child entity.

Steps to reproduce

Here's a contrived example that should help illustrate the issue:

Create the following table structure in an empty SQL Server (2016) database:

CREATE TABLE [dbo].[Person](
	[PersonId] [varchar](10) NOT NULL,
	CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED ([PersonId])
)

CREATE TABLE [dbo].[PersonRole](
	[PersonRoleId] [int] IDENTITY(1,1) NOT NULL,
	[PersonId] [varchar](10) NOT NULL,
	[RoleId] [varchar](5) NOT NULL,
	[IsActive] [bit] NOT NULL,
	CONSTRAINT [PK_PersonRole] PRIMARY KEY CLUSTERED ([PersonRoleId]),
	CONSTRAINT [FK_PersonRole_Person] FOREIGN KEY ([PersonId]) REFERENCES [dbo].[Person]([PersonId])
)

CREATE UNIQUE INDEX [IX_PersonRole_PersonId] ON [dbo].[PersonRole]([PersonId]) WHERE [IsActive] = 1

Then, create a new project in Visual Studio using the Console App (.NET Core) template. Ensure that the project targets netcoreapp2.0 then add the following NuGet packages to the .csproj file:

<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.2" />

Next, run the Scaffold-DbContext command in the Package Manager Console to reverse engineer the database (modify connection string as needed):

Scaffold-DbContext "server=.;database=ScaffoldDbContextTest;Integrated Security=SSPI" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
Expected Result:

The PersonRole navigation property on the Person class should be of type ICollection<PersonRole> since the index allows for an unlimited number of rows with the same PersonId to exist in the PersonRole table as long as no more than one of them satisfies the filter clause.

Actual Result:

The configuration code in the DbContext class appears to describe the index correctly (including the filter clause) but the entity relationship is configured as one-to-one...

modelBuilder.Entity<PersonRole>(entity =>
{
    entity.HasIndex(e => e.PersonId)
        .IsUnique()
        .HasFilter("([IsActive]=(1))");
    ...
    entity.HasOne(d => d.Person)
        .WithOne(p => p.PersonRole)
        .HasForeignKey<PersonRole>(d => d.PersonId)
        .OnDelete(DeleteBehavior.ClientSetNull)
        .HasConstraintName("FK_PersonRole_Person");
});

...and the navigation property in the Person class is therefore not a collection, as if the filter doesn't exist:

public PersonRole PersonRole { get; set; }
Further technical details

EF Core version: 2.0.2
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Database Server: SQL Server 2016 13.0.4206.0
Operating system: Windows 10 Enterprise
IDE: Visual Studio 2017 15.6.2
.NET Core SDK: 2.1.101
.NET Core runtime: 2.0.6

Dominant language
C#
Stars
14.8k
Forks
3.4k
Avg merge
2d 5h
Merged PRs (30d)
134

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/efcore

All issues in dotnet/efcore

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.