Dan Friedman
Dan Friedman

Reputation: 5228

How to make Many-to-one mapping without foreign key in EF Core?

Using EF Core 5.0, I have a PK-less entity (from a SQL view) OrderInfo, which has a column OrderDetailId. I also have an entity DiscountOrder which a PK from the columns OrderDetailId and DiscountId.

I would like to create a navigation property from Order to DiscountOrders. Such as:

public class OrderInfo
{
    public int OrderDetailId { get; set; }
    public virtual ICollection<DiscountOrder> DiscountOrders { get; set; }
}

public class DiscountOrder
{
    public int DiscountId  { get; set; }
    public int OrderDetailId  { get; set; }
}

// For reference, this entity also exists
public class Discount
{
    public int DiscountId { get; set; }
}

Obviously, there are no FKs to make use of, but I should be able to create a navigation property anyway.

I think I should be able to do this:

modelBuilder.Entity<OrderInfo>(e =>
{
    e.HasNoKey();

    e.HasMany(x => x.DiscountOrders)
        .WithOne()
        .HasPrincipalKey(o => o.OrderDetailId)
        .HasForeignKey(pb => pb.OrderDetailId)
        .IsRequired(false);
});

But a query on DbSet<OrderInfo> results in a NullReferenceException with the breakpoint landing on the HasMany() line. That said, I don't do anything with the DiscountOrders property, so the exception seems like it would have to be configuration related.

I've looked at answers to similar questions, but most answers use HasOne().WithMany() where as I'd like to keep this definition on OrderInfo since we don't really care about the other direction. How can I correctly set up this mapping?

Upvotes: 1

Views: 2048

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205819

Keyless entities (entity without key) cannot be principal of a relationship, because there is no key to be referenced by the FK property of the dependent.

Note that by EF Core terminology key is primary key. There are also alternate (unique) keys, but EF Core does not enable them for keyless types.

So basically HasNoKey() disables alternate keys and relationships to that entity. Just the exception is unhandled, hence not user friendly. For instance, if you try to predefine the alternate key referenced by .HasPrincipalKey(o => o.OrderDetailId) in advance

e.HasNoKey();
e.HasAlternateKey(o => o.OrderDetailId);

you'll get much better exception message at the second line

The key {'OrderDetailId'} cannot be added to keyless type 'OrderInfo'.

Shortly, e.HasNoKey(); and `.HasPrincipalKey(o => o.OrderDetailId); are mutually exclusive.

The only way to make it work is to define PK for OrderInfo even though it does not exist in database. In fact if OrderDetailId was supposed to be alternate key, in other words, is unique in the returned set, then you can safely map it as PK

//e.HasNoKey();
e.HasKey(o => o.OrderDetailId);

If it is not unique, then nothing can be done - you cannot map and use navigation property, and will be forced to use manual joins in L2E queries.


Update: EF Core also blocks changing "keyless"-ness once it's been set via fluent API (which has the highest configuration priority). So if you can't remove HasNoKey() fluent call because of it being generated by reverse engineering, you have to resort to metadata API to make it again "normal" entity by setting the IsKeyless property to false before defining the key, e.g.

e.HasNoKey(); // generated by scaffolding

e.Metadata.IsKeyless = false; // <-- 
e.HasKey(o => o.OrderDetailId); // now this works

Upvotes: 1

Related Questions