web-dev-qa-db-fra.com

InvalidOperationException .NET Core Entity Framework

J'ai un modèle simple

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

et dans mon DbContext

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

et dans mon contrôleur

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

Qui renvoie l'erreur:

InvalidOperationException: la colonne requise 'InterfaceID' n'était pas présente dans les résultats d'une opération 'FromSql'.

J'utilise FromSql dans d'autres méthodes similaires à celle-ci sans problème, bien que ces modèles contiennent un InterfaceId. Pourquoi cette opération attend-elle un ID d'interface alors que ce n'est pas dans le modèle? J'ai aussi essayé le ci-dessous avec le même résultat.

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

J'ai aussi essayé:

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

Après avoir déclaré via l’API fluide:

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

avec le même résultat.

Pour plus de clarté, voici le tableau dans 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]

METTRE À JOUR

J'ai examiné le code SQL généré par EF:

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

D'où provient-il InterfaceID?

4
RedEyedMonster

D'où provient-il InterfaceID?

Premièrement, il devrait être clair que cela ne vient pas du modèle "simple" montré (mais apparemment incomplet).

L'EF généré par SQL indique clairement que vous n'avez pas renommé la colonne générée par la propriété PK. De plus, il n'y a pas de colonne Discriminator et ne peut donc pas provenir d'héritage. Et le risque que vous ayez explicitement défini une propriété shadow nommée InterfaceID sans le remarquer est faible.

Tout cela, ainsi que le fait que le nom InterfaceID correspond à l'un des noms classiques EF Core pour le nom de propriété/colonne FK est pour moi une indication claire d'un FK classique introduit par une relation. Par exemple, avoir un deuxième modèle comme celui-ci:

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

Comme expliqué dans la section Relations - Propriété de navigation unique Documentation de base EF:

L'inclusion d'une seule propriété de navigation (pas de navigation inverse, ni de propriété de clé étrangère) suffit pour avoir une relation définie par convention.

et l'exemple d'accompagnement montre le modèle Blog/Post avec uniquement la propriété public List<Post> Posts { get; set; } dans Blog en surbrillance.

Tous les comportements d'exécution d'EF Core sont basés sur les métadonnées du modèle. Quelle que soit la structure de votre base de données, le plus important est ce que EF Core pense être basé sur vos classes de modèle, vos annotations de données et votre configuration fluide, et si cela correspond au schéma de la base de données. Le moyen le plus simple de vérifier cela consiste à générer la migration et à vérifier si elle correspond ou non au schéma de base de données.

Donc, si la relation est intentionnelle, vous devez mettre à jour votre base de données pour qu'elle corresponde à votre modèle. Sinon, vous devez mettre à jour votre modèle afin qu'il corresponde à la base de données - en supprimant ou en ignorant la propriété de navigation de la collection (ou en corrigeant la configuration annotation/fluente des données non valide à l'origine de la différence).

5
Ivan Stoev

D'après ma compréhension de ce problème, EF a créé une propriété Shadow Property Dans votre classe de modèle, éventuellement par une relation partiellement découverte dans votre modèle Interface.

De plus, j’ai le sentiment qu’il existe une différence entre votre ModelSnapshot utilisé par EFCore et l’état réel des tables dans la base de données (éventuellement par une migration en attente). Vérifiez deux fois comment votre InterfaceType dans <YourDbContext>ModelSnapshot.cs et vérifiez s’il ya une propriété qui vous manque.

2
WueF

Juste pour vérifier s'il y a un moyen de me reproduire, j'ai créé un exemple .NET Core Console application pour vérifier cela et dans mon cas, je suis en mesure de récupérer les données de la base de données sans exception. 

Je comprends que vous avez d’autres modèles où le même code fonctionne, et si vous déplacez le code problématique en dehors de votre solution d'origine, vous risquez d'être capable de savoir s’il manque quelque chose d’évident.

J'ai essayé de suivre votre code aussi étroitement que possible pour tenter de reproduire ce problème, où certaines des choses que je devais changer. 

Je ne sais pas quelles versions de .NET Core et EF Core vous avez utilisées. Dans mon échantillon, j'ai utilisé:

  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

J'ai crée: 

  1. Classe de modèle selon votre échantillon
  2. Classe de contexte avec OnModelCreating par votre échantillon

Exécuté après les commandes dotnet core dans le même ordre que celui indiqué ci-dessous: 

  1. dotnet restore 
  2. dotnet build 
  3. dotnet ef migrations add InitMgr 
  4. dotnet ef database update 
  5. Ajout de quelques enregistrements de test dans la table

Copiez le code de récupération de vos enregistrements, supprimez "[Interfaces]" de la requête et corrigez le code ci-dessous. 

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

J'ai pu récupérer les données de DB. 

 enter image description here

Il serait également utile si vous partagez Minimal, Complete et Verifiable exemple que quelqu'un puisse déboguer et vous aider à trouver une solution pour ce.

0
dj79

Peut-être essayer d'ajouter DatabaseGeneratedAttribute

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

Ce qui suit a fonctionné pour moi:

Insérer des données:

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
0
JohnyL