Reputation: 5228
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
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