Home > Net >  Getting a SQLite Error 19: 'UNIQUE constraint failed' with Entity Framework Core when upda
Getting a SQLite Error 19: 'UNIQUE constraint failed' with Entity Framework Core when upda

Time:04-04

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.

  • Related