Home > Blockchain >  EF Core add/save by Id OR entity reference
EF Core add/save by Id OR entity reference

Time:02-14

I have an entity I'm initiating to be saved as a record via EF Core code-first.

Sometimes I will have the related entity model - usually when I'm creating a new related entity at the same time, so it won't have an Id yet.

But sometimes I will only have the ForeignKey Id of the related entity, particularly when I'm adding just this new record related to an existing record.

I haven't been able to find anything on this specific dual-create-mode pattern, is this the correct convention in this situation?

public class ClownModel {
    public ClownModel (){}
    public ClownModel (CarModel car){
        Car = car;
    }
    public Clown (int carId){
        CarId = carId;
    }
    
    public int Id {get;set;}
    public int? CarId {get;set;}
    [ForeignKey("CarId")]
    public CarModel Car {get;set;} = null!;
}

My thought is that the FK can be nullable on the entity, so when it's also creating a new related entity it will be assigned during the save?

CodePudding user response:

Long story short, all your reasonable expectations will be met:

  • If you create a new Clown and assign CarId=1, then EF will assume you know what car you wanted to assign even if it's never seen Car 1 be downloaded before; it will create a Clown with CarId 1. If you have a relationship DB side then Car 1 will need to exist otherwise the DB will reject it
  • If you create a new Clown and a new Car assigned to its Car property, and that new car has the default value for its CarId, the EF will save the Car first, retrieve the ID and wire it into the Clown before the Clown is saved so that the relationship is satisfied on the DB side

Don't create a new Clown with a new Car that has a CarId that already exists; EF will try to insert a new Car (because it knows the entity is new) with the given ID rather than leaving the DB to create oen, which would probably result in a "cannot insert PK value to a table with IDENTITY_INSERT OFF" type error. If yo uknow the Car Id, use Route 1 above and just assign it to the Clown.CarId

CodePudding user response:

I recommend using one or the other, not both. Typically when you are doing something like creating a row and want to associate another existing row to it, you should explicitly validate that the ID of the associated entity exists to handle things more gracefully than simply waiting for a SaveChanges to fail. Creating a new Clown would pass the details for the clown and a CarId when assigning them to a specific car. In this case my Clown entity would use a Car reference with a shadow property for the CarId. I request the Car from the DbContext via the CarId and handle the situation where a Car might not be found. (Another user deleted it, a bug in the code, or someone is hacking at the request payload)

Alternatively in situations where I either need raw performance or don't care about the entity association, I will just map the Id with no navigation property. Examples of this would be a bounded context that handles background batch-like jobs, or references to FKs that I don't ever need more than the Key for anyways. Such as something like a "Status" where I do have a Status table and FK for referential integrity, but since those statuses have relevance through business logic decisions I would use an enumeration in the code to represent them. I don't care about a Status entity, just it's key.

The reason I don't recommend using both is that it presents a situation where you have two sources of truth. Clown.CarId, and Clown.Car.CarId. If you want to change a clown's car, setting Clown.CarId would have a different behaviour whether Clown.Car was loaded or not. (whether eager loaded, lazy loaded, or populated by the DbContext if it was already tracking the car when the clown was requested)

  • Related