I'm getting started with ASP.NET Core and EF Core 6. I'm trying to build a simple web api whith two models and an 1-n relationship between the models.
Model A:
public class ModelA
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public ICollection<ModelB> ModelBs { get; set; }
}
Model B:
public class ModelB
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public Guid ModelAId { get; set; }
public ModelA ModelA { get; set; }
}
When I try to create a ModelB by using the POST-endpoint of the ModelB-Controller, it expects me to pass the ModelA as well. If I do provide it, I get a duplicate key error because EF tries to create a new ModelA in the database, which causes a duplicate key error.
I must only use the ModelB-model as parameter for the post method and explicitly must not use any kind of intermediate model.
I would like to only use the ModelA id, not the entire ModelA object:
//Desired post-request
{
"stringProperty": "value",
"modelAId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
Making the ModelA reference nullable allows for post-requests as described above:
public class ModelB
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public Guid ModelAId { get; set; }
public ModelA? ModelA { get; set; }
}
But that feels wrong since an instance of ModelB must not exist without a reference to a ModelA instance. Is there any way to achieve this without using DTOs or making the reference nullable? I'm probably missing something trivial here.
CodePudding user response:
I think you should star to use DTOs on your project! For example, model B would have only the data necessary to create the item, i.e StringProperty and ModelAId, and inside of your controller, you would associate with the existing ModelA.
You can have a look on the entity framework on the link below.
CodePudding user response:
Even if you create a DTO(highly recommended, passing a whole another object just to show relationship is a waste of bandwidth and bad practice ) or not you will accept ModelAId from the user, not the whole object anyway so.
Edit:
if you want to trick the product owner. Just create a base class without ModelA prop and all the rest props in ModelB now make ModelB inherit this and add ModelA explicitly. Now create ModelBPost also inheriting from this and use this as a parameter for POST data this way the product owner knows the fields are exactly the same and you pass the verification error.
Old Answer
How about you get the model A fresh from DB and assign that to ModelB like
public IAction PostModelB(ModelB modelB)
{
modelB.ModelA = context.ModelAs.First(x => x.Id == modelB.ModelAId);
//now since efcore is tracking this it know this object already exists
context.ModelBs.Add(modelB);
}
However, sometimes EFCore screws up and you get an already tracked error(which is quite unfortunate after so much time it still can't do it properly). If this happens(which it might since you are persistent on not using a DTO and accept a whole object just for relationship) you will have to set the navigation property null and only use the ModelAId
property to insert the new record or you can get the instance which efcore holds:
var modelA = context.ModelAs.First(x => x.Id == modelB.ModelAId);
var trackedinstance = context.ChangeTracker.Entry(modelA)?.Entity ?? modelA;
modelB.ModelA = trackedinstance;
modelB.ModelAId = modelA.Id;