Optimistic Concurrency Exception During Synchronous Update Call - c#

I am attempting to modify a single boolean property on my Invoice entity.
This is my class:
public class Invoice : SoftDeletableEntity, IIsActive
{
public Invoice()
{
IsEditable = true;
}
[ForeignKey("Booking")]
public int BookingId { get; set; }
public Booking Booking { get; set; }
[ForeignKey("Customer")]
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public string OurReference { get; set; }
public string CustomerReference { get; set; }
public DateTime InvoiceDate { get; set; }
public string InvoiceNumber { get; set; }
[ForeignKey("AccountInformation")]
public int AccountInformationId { get; set; }
public AccountInformation AccountInformation { get; set; }
public string Summary { get; set; }
public List<InvoiceInformation> ImportantInformation { get; set; }
[ForeignKey("InvoiceItem")]
public List<int> InvoiceItemIds { get; set; }
public List<InvoiceLineItem> InvoiceLineItems { get; set; }
[ForeignKey("InvoiceDocument")]
public List<int> InvoiceDocumentIds { get; set; }
public List<InvoiceDocument> InvoiceDocuments { get; set; }
public string CreatedBy { get; set; }
public string Terms { get; set; }
public bool IsReversal { get; set; }
public bool IsEditable { get; set; }
public int? ParentInvoiceId { get; set; }
}
These classes are exported as a formatted PDF. That process works quite well, and then I have this code that checks whether or not the PDF was constructed correctly, then if the export succeeds, the code sets the invoice as ineditable like so:
var doc = pdf.CreateInvoicePdf(resultModel, user);
var bytes = GetPdfBytes(doc);
if (bytes != null)
{
result.IsEditable = false;
}
unitOfWork.Commit();
When I call this "unitOfWork.Commit();" I get the following exception:
System.Data.Entity.Infrastructure.DbUpdateException: 'An error occurred while saving entities that do not expose foreign key properties for their relationships.
The inner exception:
Message "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."
The most recently added foreign key reference was for CustomerId, but it is set up just like all of the other foreign keys. I have checked the database for FK = 0 for all foreign key entities and there are no missing references. Also this is running on my local, so I am certain the entity was not modified by sources other than my browser.
Any help is greatly appreciated.

Here are some possible reasons which might cause such problem:
You trying to update entity with primary key Id == 0 or another default value (Guid.Empty or string.Empty). So, verify that Id is set properly.
When you try to create a new object and tell EF that it's modified using the EntityState.Modified. Then it will throw this error as it doesn't yet exist in the database. So, try to use EntityState.Added.
You tried to update or delete a row but the row doesn't exist.

Related

Update an entity in database

I have these two entities:
public class TblUser
{
public long Userid { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string PasswordSalt { get; set; }
public string FullName { get; set; }
public virtual ICollection<TblNotify> TblNotifies { get; set; }
}
public partial class TblNotify
{
public Guid NotifyId { get; set; }
public string Message { get; set; }
public virtual TblUser ApprovalUser { get; set; }
}
The table tblNotify has a foreign key Userid.
DbContext was initially built as singleton service.
I call an update method:
uow.GetRepository<tblNotify>().Update(item);
It almost always updates successfully, but rarely, I got this error:
The instance of entity type 'Tbluser' cannot be tracked because another instance with the same key value for {'Userid'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
It's weird because I only update the tblNotify table. How could I investigate this error further?
Any help will be appreciated.

EF Core introducing foreign key constraint on table may cause cycles or multiple cascade paths

I have a User entity:
public class User
{
[Key]
public string Username { get; set; }
}
and a Message entity:
public class Message
{
public int Id { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
public User Sender { get; set; }
public User? Recipient { get; set; }
}
and my context is configured like this:
public class DummyContext: DbContext
{
public DummyContext(DbContextOptions<DummyContext> options)
: base(options){ }
public DbSet<Message> Messages { get; set; } = null!;
public DbSet<User> Users { get; set; } = null!;
}
Everything works fine and i can add-migration and update-database
Then i try to add another Invoice entity:
public class Invoice
{
public int Id { get; set; }
public User Sender { get; set; }
public User Recipient { get; set; }
public string? Description { get; set; }
public DateTime Date{ get; set; }
public float Amount { get; set; }
}
and add the DbSet to the context
...
public DbSet<Invoice> Invoices{ get; set; } = null!;
...
This time i add-migration and update-database but it gives me an error:
The introduction of the FOREIGN KEY constraint 'FK_Invoices_Users_SenderUsername' in the 'Invoices' table can result in the creation of loops or more propagation paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION or change the other FOREIGN KEY constraints.
And i cant quite figure out why it wasnt giving me the same error for the messages,
I want the message to delete on cascade if the user is deleted and that works fine but as soon as i try to do the same thing with the invoices it gives me this error.
I dont want to add OnDelete No action since i want the invoices to get deleted on user deletion too.

Entity Framework Core default values for missing columns

I have a sqlite database which has some tables and columns like the following:
int Id
text Name
text Comment
...
And my object in my project looks like this:
Public Class Entry {
public int Id { get; set; }
public String Name { get; set; }
public String Comment { get; set; }
public String Additional { get; set; }
}
This can happen, because my programm need to handle different versions of the database.
EF Core now trys to access the Additional field of the database but returns an error that it cannot find the field. (Expected behaviour)
Now my question is, if there is a way to ignore this error and return a default value for the property?
I could bypass the error by making the properties nullable. But i don't want to check each property with .HasValue() before accessing it. Because the real database has 50+ columns in the table.
https://www.entityframeworktutorial.net/code-first/notmapped-dataannotations-attribute-in-code-first.aspx
Put NotMapped as an attribute on the Additional field:
using System.ComponentModel.DataAnnotations.Schema;
Public Class Entry {
public int Id { get; set; }
public String Name { get; set; }
public String Comment { get; set; }
[NotMapped]
public String Additional { get; set; }
}
This tells EF that the field is not a column in the database.
I would advise you to split your domain object from that persisted dto object. That way you can have different dtos with different mappings. Now you can instantiate your domain object with your dto and decide inside your domain object what values are the correct default values.
public class Entry
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public string Additional { get; set; }
}
public class EntryDtoV1
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
}
public class EntryDtoV2
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public string Additional { get; set; }
}
Now you only need to create some kind of factory that creates the correct repository depending on what database version you query.

EF Core 2.0 - Navigation properties can only participate in a single relationship

Let's say I have these models:
public class Component
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public List<ComponentUpdate> Updates { get; set; }
public ComponentUpdate LastUpdate { get; set; }
}
public class ComponentUpdate
{
public int Id { get; set; }
public DateTime Timestamp { get; set; }
public Component Component { get; set; }
public string Message { get; set; }
}
The reason I'm saving the LastUpdate field instead of manually pulling it according to the highest 'TimeStamp' is because of speed. It would be faster to store a reference instead of checking the entire list every request.
When I'm trying to migrate the DB it throws an error saying I cannot have my properties participate in more than a single relationship.
I'm mapping the relationships in my context class and I don't think I'm doing it right since I have ComponentUpdate.Component mapped twice.
I've looked on several solutions but some were outdated and some just did not fit this scenario.
Thanks for helping.
Edit
Mapping accordingly:
modelBuilder.Entity<Component>().HasMany(c => c.Updates).WithOne(u => u.Component);
modelBuilder.Entity<ComponentUpdate>().HasOne(u => u.Component).WithOne(c => c.LastUpdate);

Can't figure out a solution to 'The model backing the 'TasksJobsDbContext' context has changed since the database was created'

I've been trying to solve this issue for 3 days now, and just can't figure it out. I've searched the web a lot and most of the solutions suggest using null initializer which doesn't solve my problem:
This is my model:
public class JobsRecord
{
[Key]
public int Index { get; set; }
[Required]
[Index]
public int TaskID { get; set; }
[ForeignKey("TaskID")]
public virtual TasksRecord Task { get; set; }
[Required]
[Index]
public Int64 DeviceID { get; set; }
[Required]
public DateTime NextRunTimestamp { get; set; }
public DateTime TimeOfRun { get; set; }
public TasksJobsMisc.RunResultEnum RunResult { get; set; }
public int JobResult { get; set; }
public double JobResultValue { get; set; }
public string ExtendedResults { get; set; }
public string Comments { get; set; }
}
public class JobsRecordHistory
{
[Key]
public int Index { get; set; }
[Required]
[Index]
public int TaskID { get; set; }
[ForeignKey("TaskID")]
public virtual TasksRecord Task { get; set; }
[Index]
public Int64 DeviceID { get; set; }
public TasksJobsMisc.RunResultEnum RunResult { get; set; }
public int JobResult { get; set; }
public double JobResultValue { get; set; }
public string ExtendedResults { get; set; }
public string Comments { get; set; }
}
public class TasksRecord
{
[Key]
public int TaskID { get; set; }
[Required]
public int Interval { get; set; } //minutes
[Required]
public string TaskObjName { get; set; }
[Required]
[Index]
public ReporterType Type { get; set; }
public int MaxDaysSinceLastReport { get; set; }
public int MinVersion { get; set; }
public int MaxVersion { get; set; }
public int FailureInterval { get; set; } //minutes
public string Site { get; set; }
public string TaskName { get; set; }
public string JSONConfig { get; set; } //extra configuration
public int ParallelLevel { get; set; } //control parallelism for each task
public int EnableDisable { get; set; }
}``
What I'm trying to do is to add the last field, EnableDisable, to the TaskRecord class. Up until now I worked with EF Code First migrations and was pretty happy about it, but now, when I try to add the EnableDisable field to that class and run 'Add-Migration TasksEnable' and then Update-Database, although it runs successfully, When I run my code I get the 'The model backing the 'TasksJobsDbContext' context has changed since the database was created' error where I use my context for the first time:
using (var db = new TasksJobsDbContext())
{
var blankjobs = db.JobsRecords.Include("Task").Where(x => x.RunResult == TasksJobsMisc.RunResultEnum.Blank);
Log.DebugFormat("Found {0} jobs", blankjobs.Count());
jobs = blankjobs.Where(x => DateTime.Now.CompareTo(x.NextRunTimestamp) > 0).ToList();
}
I know I can 'restart' the migrations like I read in some other stackoverflow thread, by dropping the migrations table and directory and just start from scratch and let the database and the migrations be recreated, but obviously I don't want to lose my data and more important, I want to figure it out so that I can handle it in the future.
I tried the null initializer and it didn't help.
Every time I get this error I just revert the last migration by 'Update-Database -TargetMigration: 'my last working migration'' and that fixes the problem.
I tried some debugging to add some more data:
using (var db = new TasksJobsDbContext())
{
Log.DebugFormat("compatible (with metadata): {0}", db.Database.CompatibleWithModel(true));
Log.DebugFormat("compatible: {0}", db.Database.CompatibleWithModel(false));
Log.DebugFormat("exists: {0}", db.Database.Exists());
db.Database.Initialize(true);}
What I get is (initializer set do Database.SetInitializer(null);
or to Database.SetInitializer(new CreateDatabaseIfNotExists());, same results for both)
: false, false, true, then this exception again...
I'm using VS2013, EF 6.9.6.0, Code First, MySQL, and this is my first project with EF (went pretty well up until now).
So, as we've found in comments everything seems ok, except the error itself..
I can't guess the actual reason of it, but have some proposals to mitigate the outcome.
Generate scripts for all the data you have (SQL management studio has such possibility, for example), delete your db, run all the migrations for db creation, restore data and work like this never happened (actually I've never met such a weird behaviour before, so with high probability it will not repeat)
Delete your MigrationHistory table and all the migrations (code and resources), then create migration starting from actual db state with use of -IgnoreChanges flag for add-migration command.
As written in docs for -IgnoreChanges.
Scaffolds an empty migration ignoring any pending changes detected in the current model. This can be used to create an
initial, empty migration to enable Migrations for an existing
database. N.B. Doing this assumes that the target database schema is
compatible with the current model.
But with this approach you won't be able to recreate your db on another server using migrations only.

Categories

Resources