简体   繁体   中英

.NET Core Entity Framework InvalidOperationException

I have a simple model

[Table("InterfaceType")]
public class InterfaceType
{
    [Key]
    public int InterfaceTypeId { get; set; }
    public string Description { get; set; }
}

and in my DbContext

public DbSet<InterfaceType> InterfaceTypes { get; set; }

and in my controller

List<InterfaceType> types = _context.InterfaceTypes.FromSql(
            "SELECT * FROM [Interfaces].[Control].[InterfaceType]").ToList();

Which is returning the error:

InvalidOperationException: The required column 'InterfaceID' was not present in the results of a 'FromSql' operation.

I am using FromSql in other methods similar to this with no issue although those models do contain an InterfaceId. Why does this operation expect an InterfaceId when it is not in the model. I have also tried the below with the same result.

List<InterfaceType> types = _context.InterfaceTypes.FromSql(
            "SELECT InterfaceTypeId, Description FROM [Interfaces].[Control].[InterfaceType]").ToList();

I have also tried:

interfacesOverview.SelectedInterface.InterfaceTypes = _context.InterfaceTypes.ToList();

After declaring via the fluent api:

 protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
       modelBuilder.Entity<InterfaceType>().ToTable("InterfaceType", "Control");
    }

with the same result.

For clarity here is the table in MSSQL:

    CREATE TABLE [Control].[InterfaceType](
    [InterfaceTypeId] [tinyint] NOT NULL,
    [Description] [varchar](25) NULL,
 CONSTRAINT [PK_InterfaceType] PRIMARY KEY CLUSTERED 
(
    [InterfaceTypeId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

UPDATE

I've looked at the SQL that EF is generating:

    SELECT [i].[InterfaceTypeId], [i].[Description], [i].[InterfaceID] FROM [Control].[InterfaceType] AS [i]

Where is it getting InterfaceID from?

Where is it getting InterfaceID from?

First, it should be clear that it's not coming from the shown "simple" (but apparently incomplete) model.

The EF generated SQL clearly indicates that you didn't rename the PK property generated column, also there is no Discriminator column, so it cannot be coming from inheritance. And the chance that you have explicitly defined a shadow property called InterfaceID and not noticing it is small.

All this, along with the fact that the name InterfaceID matches one of the EF Core conventional names for FK property/column name for me is a clear indication of a conventional FK introduced by a relationship. For instance having a second model like this:

public class Interface
{
    public int ID { get; set; }
    // or
    // public int InterfaceID { get; set; }
    public ICollection<InterfaceType> InterfaceTypes { get; set; }
}

As explained in the Relationships - Single Navigation Property EF Core documentation topic:

Including just one navigation property (no inverse navigation, and no foreign key property) is enough to have a relationship defined by convention.

and the accompanying example shows Blog / Post model with only public List<Post> Posts { get; set; } public List<Post> Posts { get; set; } public List<Post> Posts { get; set; } property in Blog highlighted.

All EF Core runtime behaviors are based on model metadata. It doesn't matter what is the structure of your database, the more important is what EF Core thinks it is base on your model classes, data annotations and fluent configuration, and if that matches the database schema. The easier way to check that is to generate migration and check if it matches the database schema or not.

So if the relationship is intentional, then you have to update your database to match your model. Otherwise you need to update your model to match the database - by removing or ignoring the collection navigation property (or correcting the invalid data annotation / fluent configuration causing the discrepancy).

My understanding of this problem, is that EF created a Shadow Property inside your model class, possibly by partially discovered relationship in your Interface model.

Also I feel there is a mismatch between your ModelSnapshot used by EFCore and real state of tables in Database (possibly by pending migration). Double check, how your InterfaceType in <YourDbContext>ModelSnapshot.cs , and check if there's a property you are missing.

First why not use

List<InterfaceType> types = _context.InterfaceTypes.ToList();

Secondly did you apply any changes to the model and forget to persist this to the database, as it could be that the column is correct in your class but not in your database. This is often something i forget to do when using a Code-FirstModel.

Here is some additional info on FromSQL :- https://docs.microsoft.com/en-us/ef/core/querying/raw-sql

More detail on migration here:- https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/

I hope this helps.

My guess is that you also have an "Interface" table registered in the context that holds a reference to the InterfaceType. Interface would have an InterfaceTypeId field declared, however with EF, if you are using HasOne with a ForeignKey, check that you haven't accidentally assigned something like:

.HasOne(x => x.InterfaceType).WithOne().HasForeignKey<InterfaceType>("InterfaceId");

In the case of an Interface having an InterfaceType it would be mapped more like:

.HasOne(x => x.InterfaceType).WithMany();

This might have crept into one of your other associated entities. Often these are typos where the autocomplete picked the wrong type without you noticing. If that mapping exists on any of your classes, EF will be expecting to find an InterfaceId column on InterfaceType. Do a search on HasForeignKey<InterfaceType> and see if that turns up anything out of the ordinary.

Maybe try to add DatabaseGeneratedAttribute

[Table("InterfaceType")]
public class InterfaceType
{
   [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity),Key()]
   public int InterfaceTypeId { get; set; }
   ...

Just to check if there is a way I can reproduce, I created a sample .NET Core Console application to check this and in my case, I am able to retrieve the data from DB without any exception.

I understand you have other models where the same code is working, and if you move the problematic code outside your original solution, you might be able to figure out if there is something obvious you are missing.

I tried to follow your code as closely as possible in attempt to reproduce this issue, where some of the things I had to change.

I don't know which .NET Core and EF Core versions you have used. In my sample, I used:

  1. .NET Core 2.2
  2. Microsoft.EntityFrameworkCore 2.2
  3. Microsoft.EntityFrameworkCore.Design 2.2
  4. Microsoft.EntityFrameworkCore.SqlServer 2.2
  5. Microsoft.EntityFrameworkCore.Tools 2.2

I created:

  1. Model Class as per your sample
  2. Context Class with OnModelCreating per your sample

Executed following dotnet core commands in the same order as listed below:

  1. dotnet restore
  2. dotnet build
  3. dotnet ef migrations add InitMgr
  4. dotnet ef database update
  5. Added few test records in the table

Copied your records retrieval code, removed "[Interfaces]" from the query and debugged the code below.

        var _context = new InterfaceTypeContext ();
        List<InterfaceType> types = _context.InterfaceTypes.FromSql ("SELECT * FROM [Control].[InterfaceType]").ToList ();

I was able to retrieve the data from DB.

在此输入图像描述

It would also help if you share Minimal, Complete, and Verifiable example for someone to debug and help you find a solution for this.

The following has worked for me:

Insert some data:

insert into [Control].[InterfaceType] values (1, 'Desc1'), (2, 'Desc2');

C#:

class SOContext : DbContext
{
    public DbSet<InterfaceType> InterfaceTypes { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var conn_string = @"Server=(localdb)\mssqllocaldb;Database=Interfaces;Trusted_Connection=Yes;";
        optionsBuilder.UseSqlServer(conn_string);
    }
}

[Table("InterfaceType", Schema = "Control")]
public class InterfaceType
{
    [DatabaseGenerated(DatabaseGeneratedOption.None), Key]
    public byte InterfaceTypeId { get; set; }
    public string Description { get; set; }
    public override string ToString() =>
        $"Id: {InterfaceTypeId} | Description: {Description}";
}

// Output:
// Id: 1 | Description: Desc1
// Id: 2 | Description: Desc2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM