I have an invoice object that has a client object as a property. I am getting this SQLite Error 19: 'UNIQUE constraint failed: Client.InvoiceId' when I try to update my Invoice object. I tried doing what another article on the same problem suggested, and that is adding an Attach statement before calling SaveChanges() but that didn't work:
_context.Invoices.Attach(invoice);
Here's my data model:
public class Invoice
{
public string InvoiceId { get; set; } = GenerateID.GenerateInvoiceID();
public string Description { get; set; }
public List<InvoiceItem> Items { get; set; }
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public int TotalPrice { get; set; }
public string Status { get; set; } = "pending";
public Client Client { get; set; }
public string BillFromAddress { get; set; }
public string BillFromCity { get; set; }
public string BillFromCountry { get; set; }
public string BillFromPostal { get; set; }
}
public class Client
{
public string ClientId{ get; set; } = GenerateID.GenerateClientID();
public string InvoiceId { get; set; }
[Required]
public string ClientName { get; set; }
[Required]
public string ClientEmail { get; set; }
[Required]
public string ClientAddress { get; set; }
[Required]
public string ClientCity { get; set; }
[Required]
public string ClientCountry { get; set; }
[Required]
public string ClientPostal { get; set; }
}
public class InvoiceItem
{
public string InvoiceItemId { get; set; } = GenerateID.GenerateItemID();
public string InvoiceId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
public int Price { get; set; }
public InvoiceItem()
{
}
public InvoiceItem(string itemName, int quantity, int price)
{
Name = itemName;
Quantity = quantity;
Price = price;
}
}
Here is the service that updates the invoice:
public async void EditInvoice(InputModel input, string id)
{
var invoice = await _context.Invoices.FindAsync(id);
if (invoice == null) { throw new Exception("Unable to find the invoice"); }
invoice.Items = input.Items;
invoice.Description = input.Description;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
await _context.SaveChangesAsync();
}
Here's my InputModel class:
public class InputModel
{
[Required]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public Client Client { get; set; }
public List<InvoiceItem> Items { get; set; } = new List<InvoiceItem>(16);
[Required]
public string BillFromAddress { get; set; }
[Required]
public string BillFromCity { get; set; }
[Required]
public string BillFromCountry { get; set; }
[Required]
public string BillFromPostal { get; set; }
public void PopulateItems()
{
for (int i = 0; i < Items.Capacity; i )
{
Items.Add(new InvoiceItem());
}
}
}
Here's my CreateInvoice PageModel
public class CreateInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public CreateInvoiceModel(InvoiceService service)
{
_service = service;
}
public void OnGet()
{
Input = new InputModel();
Input.PopulateItems();
}
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
_service.AddInvoice(Input);
return RedirectToPage("/Index");
}
return Page();
}
}
Here's my EditInvoice PageModel
public class EditInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public string InvoiceId { get; set; }
public EditInvoiceModel(InvoiceService service)
{
_service = service;
}
public async void OnGet(string id)
{
Invoice invoice = await _service.GetInvoice(id);
InvoiceId = invoice.InvoiceId;
Input = new InputModel();
Input.Items = invoice.Items;
Input.BillFromAddress = invoice.BillFromAddress;
Input.BillFromCity = invoice.BillFromCity;
Input.BillFromPostal = invoice.BillFromPostal;
Input.BillFromCountry = invoice.BillFromCountry;
Input.Client = invoice.Client;
Input.InvoiceDate = invoice.InvoiceDate;
Input.PaymentTerms = invoice.PaymentTerms;
Input.Description = invoice.Description;
}
public async Task<IActionResult> OnPost(string id)
{
if(ModelState.IsValid)
{
_service.EditInvoice(Input, id);
return RedirectToPage("/ViewInvoice", new { id = id });
}
return Page();
}
}
Here's my DbContext class:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Invoice> Invoices { get; set; }
}
CodePudding user response:
What i assume is happening is that the ChangeTracker tracks the Client you set in your EditInvoice method as a new entity and tries to insert it into the database. But because you mapped Client and Invoice as a 1 to 1 relation the insert fails.
So since one client can really have multiple invoices you need to make the following changes:
Client
Remove the InvoiceId
public class Client
{
public string ClientId { get; set; } = GenerateID.GenerateClientID();
// public string InvoiceId { get; set; }
/* omitted for brevity */
}
Really delete this line. I just commented it out to make it clear.
Invoice
Add the ClientId
public class Invoice
{
public string InvoiceId { get; set; } = GenerateID.GenerateInvoiceID();
public string ClientId { get; set; }
/* omitted for brevity */
}
InputModel
Change the Client
object to ClientId
public class InputModel
{
// public Client Client { get; set; }
public string ClientId { get; set; }
/* omitted for brevity */
}
EditInvoice method
Update the ClientId instead of the whole Client object
invoice.ClientId = input.ClientId;
You can read more about relationships in EntityFrameworkCore here.
Let me know if this worked for you.