简体   繁体   中英

EF Core 5 One-to-many relationship problem

In this example a user has zero or many bills, one bill can be assigned to one user. Bill can also be created but never assigned.

public class User
{
  public int Id{ get; set; }   
  public List<Bill> bills{ get; set; }
}
        
public class Bill
{
  public int Id { get; set; }
        
  public int userId{ get; set; }
  public User user{ get; set; }
}

I've also added this in my DB context configuration:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
 modelBuilder.Entity<Bill>()
             .HasOne(b => b.user)
             .WithMany(u => u.bills)
             .HasForeignKey(b => b.userId);
}

I've realized it through a unit of work + repository pattern. In my BillService.cs I would like to have a method that allows me to update/add a bill and assign it to a user.

If the user doesn't exist in DB it should add it. If the user exists it should update it.

I've tried two approaches. First:

public async Task<void> AddUpdateBill(AddBillModel model){
    Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
    
    if( unitOfWork.UserRepo.GetById(model.userId) == null){
        unitOfWork.UserRepo.Insert(model.user);
    }else{
        unitOfWork.UserRepo.Update(model.user);
    }
    bill.user = model.user;
    unitOfWork.BillRepository.Update(bill);
    unitOfWork.Save();
}

Second:

public async Task<void> AddUpdateBill(AddBillModel model)
{
    Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
    bill.user = model.user;
    unitOfWork.BillRepository.Update(bill);
    unitOfWork.Save();
}

In both cases, I've got the problem of duplicated primary-key or entity already tracked.

Which is the best approach or the right way to do it?

EDIT: Sorry, BillRepo and BillRepository are the same class.

public async Task<Bill> GetByID(int id)
{
   return await context
           .bill
           .Include(b => b.user)
           .Where(b=> b.id == id)
           .FirstOrDefaultAsync();
}

public void Update(Bill bill)
{
   context.Entry(bill).CurrentValues.SetValues(bill);
}

The first approach seems more right (to me). First of all, comply with the naming rules: all properties must begin with upper case characters. "Bills", "UserId", "User" in your case.

if( unitOfWork.UserRepo.GetById(model.userId) == null){
    unitOfWork.UserRepo.Insert(model.user);
}else{
    unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;

You don't need it here

bill.user = model.user;

because you have just attached your entity to context and updated/inserted it.

Also, don't forget to format your code, for example https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/inside-a-program/coding-conventions

It would be useful to consider inserting/updating your entities not straight from the model, something like:

if( unitOfWork.UserRepo.GetById(model.userId) == null){
    var user = new User 
    {
       //set properties
    };
    unitOfWork.UserRepo.Insert(user);
    unitOfWork.Save();
    bill.userId = user.Id;
}

Here:

if( unitOfWork.UserRepo.GetById(model.userId) == null){...

you retrieve the User from UserRepo but don't assign it to any variable. This may cause the exception stating that there are multiple tracked entities with the same ID.

Try to retrieve (including bills) or create the User entity and add the new bill in there. Then insert User entity to DB (if it was not there) and simply Save your work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM