Entity Framework insert conflicts with another foreign key

后端 未结 1 938
花落未央
花落未央 2021-01-28 10:11

I am having difficulty inserting data with its related entities.

public class Status : Entity, IAggregateRoot
 {
    //other properties
     public readonly List         


        
相关标签:
1条回答
  • 2021-01-28 10:42

    This is most likely due to the disassociated entities. What is the source of the Log & LogFiles properties? My guess is that these are coming from a web client?

    To outline the problem with passing entities: Lets look at a Photo entity which has a Status reference.

    public class Status
    {
       public int StatusId { get; set; }
       public string Name { get; set; }
    }
    public class Photo
    {
       public int PhotoId { get; set; }
       public virtual Status Status { get; set; }
    }
    

    Now if I go and load a set of photos from a DbContext I might bring back 2 photos with a status of "New".

    As far as "instances" go, I would have:

     Photo (ID: 1)  \
                      ==> Status (ID: 1 [New])
     Photo (ID: 2)  /
    

    The problem is when I send those disconnected entities back to something like Controller, they are de-serialized and would look like the following:

     Photo (ID: 1)  ==> Status (ID: 1 [New])
     Photo (ID: 2)  ==> Status (ID: 1 [New])
    

    In your case you are passing back a new Photo (fine) but it should be associated to an existing Status. Your Photo entity will probably be set up to generate a PK, but a lookup like Status would not be. Either way, if EF doesn't "know" about the Status, it will be treated as a New entity along with the photo. This leads to a FK constraint as EF tries to insert Status ID 1.

    Passing Entities back to Controllers leads to all kinds of problems. If you were performing an Edit of a Photo for instance, passing back Photo ID 1, you would find that you would need to somehow tell EF about Photo #1 (using Attach and setting the State to Modified for example), and then also face FK errors around any associated entities to the photo. Attaching related entities (like Status) will solve your problem initially, but then leads to further complications like above where multiple references to the same Status are actually separate instances of a Status object. Calling attach on the first instance will work, but then you will get an exception as soon as you save something in that context with the same status. Different references and EF will complain that an instance with the same ID is associated with the context if you try and attach the 2nd.

    Attaching entities from a client itself is a dangerous practice because you are implicitly trusting the data coming back from the client. A savvy, malicious user could easily modify the data in any number of ways beyond what your web page allows, corrupting your data store.

    The main advice I give to devs around EF is "don't pass entities around". Nothing good becomes of it. :) If you pass Log view models, photo view models, etc. then it reduces the amount of data being shipped back and forth between server and client (making the system faster and less resource intensive) and forces you to think about the data coming back.

    For instance, if I take back a LogInsertViewModel and a set of associated PhotoInsertViewModels

    public async Task<Log> AddAsync(LogInsertViewModel logVm, ICollection<PhotoInsertViewModel> photoVms)
    {
       // TODO: Validate that the log & files are correct/complete and applicable to the current session user...
    
       // If I need to lookup values from collections... (1 hit to DB to get all applicable)
       statusIds = photoVms.Select(x => x.StatusId).ToList();
       var statuses = context.Statuses.Where(x => statusIds.Contains(x.StatusId)).ToList();
    
       // Alternatively if I know all new photos were going to be associated a "New" status...
       var newStatus = context.Statuses.Single(x => x.Status = Statuses.New);
    
       // Create a Log.
       var log = new Log
       {
          //.. copy values.
    
    
          Photos = photoVms.Select(x => new Photo
          {
             // copy values.
             Status = statuses.Single(s => s.StatusId = x.StatusId); // or newStatus
          }).ToList();
       };
       context.Logs.Add(log);
       context.SaveChanges();
    }
    
    0 讨论(0)
提交回复
热议问题