dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.82k stars 3.2k forks source link

Having a keyless detail scaffolds wrong code #18633

Closed MihaMarkic closed 4 years ago

MihaMarkic commented 5 years ago

When having a keyless table in SQL server, scaffolding would generate entity navigation code between master and detail. The code fails at runtime when context is created. I assume the code shouldn't throw an exception and probably scaffolding shouldn't generate it in first place. Is this a scaffolding or EF Core issue?

'The navigation '' cannot be added because it targets the keyless entity type 'Detail'. Navigations can only target entity types with keys.'

Steps to reproduce

Create a master and detail tables in sql server with detail having no PK. Scaffold the database and make an EF query. The code will throw an exception in

modelBuilder.Entity<Detail>(entity =>
entity.HasNoKey();
...
entity.HasOne(d => d.Master)
                    .WithMany(p => p.Detail)
...

Exception: 'The navigation '' cannot be added because it targets the keyless entity type 'Detail'. Navigations can only target entity types with keys.' -->

Further technical details

EF Core version: 3.0.0 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: ASP.NET Core 3.0 Operating system: Windows 10 IDE: (e.g. Visual Studio 2019 16.3.6)

divinebovine commented 5 years ago

I am having the same issue with a scaffolded database, but I am unable to navigate any entity. It's as if the existence of an entity without a key is breaking navigation.

Example Model:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MyDb.Models
{
    public partial class AccessLog
    {
        [Key]
        [Column("UID")]
        public int Uid { get; set; }
        [Column(TypeName = "datetime")]
        public DateTime LogTime { get; set; }
        [Required]
        [StringLength(50)]
        public string Instance { get; set; }
        [Required]
        [StringLength(24)]
        public string Event { get; set; }
        [StringLength(128)]
        public string Username { get; set; }
        [StringLength(24)]
        public string Result { get; set; }
        public string Details { get; set; }
    }
}

Excerpt from Context.cs:

// omitting code
public virtual DbSet<AccessLog> AccessLog { get; set; }
// omitting code

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<AccessLog>(entity =>
    {
        entity.Property(e => e.Details).IsUnicode(false);

        entity.Property(e => e.Event).IsUnicode(false);

        entity.Property(e => e.Instance).IsUnicode(false);

        entity.Property(e => e.Result).IsUnicode(false);

        entity.Property(e => e.Username).IsUnicode(false);
    });

    // omitting code
}

Unit test with exception:

[Fact]
public async void Test_Insert_Access_Log()
{
    var options = new DbContextOptionsBuilder<SMPLContext>()
        .UseInMemoryDatabase(databaseName: nameof(Test_Insert_Access_Log))
        .Options;

    using var context = new Context(options);
    var accessLog = new AccessLog
    {
        LogTime =  DateTime.UtcNow,
        Instance =  "instance",
        Event =     "event",
        Username =  "user",
        Result =    "result",
        Details =   "details"
    };

    var logCount = await context.AccessLog.CountAsync().ConfigureAwait(false);
    Assert.Equal(0, logCount);

    context.AccessLog.Add(accessLog);
    await context.SaveChangesAsync().ConfigureAwait(false);

    logCount = await context.AccessLog.CountAsync().ConfigureAwait(false);
    Assert.Equal(1, logCount);
}

Exception: image

ajcvickers commented 5 years ago

Note for triage: talked to @AndriySvyryd and it looks like scaffolding should be generating a uni-direction navigation property here. For example:

entity.HasOne(d => d.Master).WithMany()

This is because keyless entity types cannot have incoming navigations, but can be at the dependent end of a unidirectional navigation to the principal.

smitpatel commented 5 years ago

It could also be HaseOne().WithOne();

ajcvickers commented 5 years ago

Note from triage: we should generate a unidirectional relationship here.

Pzixel commented 4 years ago

@bricelam still facing this issue on 5.0.0-preview.2.20120.8. Can I do anything to mitigate it?


Fixed it by replacing HasNoKey() with my surrogate key .HasKey(t => new { t.CarId, t.PersonId });. Not sure if it how it should be, but it worked. At least I can perform readonly queries.

DustSwiffer commented 4 years ago

Fixed it by replacing HasNoKey() with my surrogate key .HasKey(t => new { t.CarId, t.PersonId });. Not sure if it how it should be, but it worked. At least I can perform readonly queries.

Was the error occured after running the scaffold or was this a mistake made by yourself and did you fix it yourself afterwards?

Pzixel commented 4 years ago

I didn't spot any errors during scaffolding, I did fix it manually afterwards when scaffolded DbContext failed to connect to the actual DB>

MuhammadTalha10 commented 4 years ago

I am facing same issue is there any solution ?

angelaki commented 3 years ago

Is there a solution, yet? Still exists with EF6

ajcvickers commented 3 years ago

@angelaki Please file a new issue and include the SQL for the tables that are being scaffolded and the code that is scaffolded so that we can investigate.

angelaki commented 3 years ago

@ajcvickers so it actually should work? Since my use-case, at least this time 😉, is quite straight forward I'd say. If so, I'll create one.

ajcvickers commented 3 years ago

@angelaki This issue was fixed in version 3.1.3, as the milestone indicates.