Entity Framework sets property to null when saving - c#

I got a weird issue I can't explain.
I'm working with EF here. Got a method that copies some values from a wrapper object into the object I already have in the database. The object in the database has to be updated with the values in the wrapper object.
Here's the code:
private void UpdateAudit(AuditWrapper audit, DatabaseAccess dbAccess)
{
var foundAudit = dbAccess.Audits.Include("Auditors").SingleOrDefault(_ => _.Audit_ID == audit.Audit_ID);
if(foundAudit != null)
{
foundAudit.Auditorennamen = audit.Auditorennamen;
foundAudit.AuditTarget = audit.AuditTarget;
foundAudit.Scopes = audit.Scopes;
foundAudit.Location = audit.Location;
foundAudit.Address = audit.Address;
foundAudit.Auditors.Clear();
foreach (var item in audit.Auditors)
{
var usr = dbAccess.Users.SingleOrDefault(_ => _.Username == item.Username);
if (usr != null)
{
var uta = dbAccess.User_To_Audit.Create();
uta.Function = item.Function;
uta.User = usr;
uta.Audit_GeneratedID = foundAudit.GeneratedID;
uta.Audit = foundAudit;
foundAudit.Auditors.Add(uta);
}
}
}
dbAccess.SaveChanges();
}
In the User_To_Audit object the Audit is set to null, even though it wasn't null when I selected it from the database.
No idea why it is set to null when I use db.SaveChanges().
Well, I don't know if the property is really null, but I can tell it saves null into the database in the foreign key column.
I've already tried to only set the GeneratedID or only set the Audit or set none of them. Every time the same effect.
[ForeignKey("Audit")]
public int Audit_GeneratedID { get; set; }
public virtual Audit Audit { get; set; }
Please help
update:
Audit has a List<User_To_Audit>.
User_To_Audit has a reference to the Audit it belongs to.
public class Audit
{
[Key]
public int GeneratedID { get; set; }
[Index("Audit_ID", IsUnique = true)]
public int Audit_ID { get; set; }
.
.
.
public virtual List<User_To_Audit> Auditors { get; set; }
}
public class User_To_Audit
{
[Key]
public int ID { get; set; }
public virtual User User { get; set; }
[ForeignKey("Audit")]
public int Audit_GeneratedID { get; set; }
public virtual Audit Audit { get; set; }
public virtual AuditorFunction Function { get; set; }
}

Related

Entity Framework setting foreign key, primary key violation

When trying to create a new database entry of type TestForm2 I include the related object Unit Type's ID as a foreign key, except when I perform context.SaveChanges() after adding the new model I get the following SQL exception:
SqlException: Violation of PRIMARY KEY constraint 'PK_dbo.UnitTypes'. Cannot insert duplicate key in object 'dbo.UnitTypes'. The duplicate key value is (2d911331-6083-4bba-a3ad-e50341a7b128). The statement has been terminated.
What this means to me is that it thinks that the foreign entry I'm trying to relate to the new model is instead a new object that it's attempting to insert into the UnitTypes table and failing because it sees an existing entry with the same primary key.
For context (pun not intended), this is my data context, the database model, and the erroring "Create" function.
public class DataContext : IdentityDbContext<ApplicationUser>
{
public DataContext() : base("DefaultConnection")
{
}
public static DataContext Create()
{
return new DataContext();
}
public DbSet<SafetyIncident> SafetyIncidents { get; set; }
public DbSet<ProductionLine> ProductionLines { get; set; }
public DbSet<ProductionOrder> ProductionOrders { get; set; }
public DbSet<SerialOrder> SerialOrder { get; set; }
public DbSet<QualityError> QualityErrors { get; set; }
public DbSet<PSA> PSAs { get; set; }
public DbSet<TestStation> TestStations { get; set; }
public DbSet<ProductionGoal> ProductionGoals { get; set; }
public DbSet<DailyWorkStationCheck> DailyWorkStationChecks { get; set; }
public DbSet<TestForm> TestForms { get; set; }
public DbSet<User> AppUsers { get; set; }
public DbSet<Options> Options { get; set; }
public DbSet<DriveList> DriveSerials { get; set; }
public DbSet<MRPController> MRPControllers { get; set; }
public DbSet<TestOption> TestOptions { get; set; }
public DbSet<UnitType> UnitTypes { get; set; }
public DbSet<UnitTypeMap> UnitTypeMaps { get; set; }
public DbSet<TestForm2> TestForm2s { get; set; }
public DbSet<TestFormSection> TestFormSections { get; set; }
public DbSet<TestFormSectionStep> TestFormSectionSteps { get; set; }
}
public class TestForm2 : BaseEntity
{
public string SerialNumber { get; set; }
public string MaterialNumber { get; set; }
public string UnitTypeId { get; set; }
public UnitType UnitType { get; set; }
public bool UsesStandardOptions { get; set; }
public bool OptionsVerified { get; set; } // This will only be used when UsesStandardOptions is true, otherwise its value doesn't matter
public ICollection<TestOption> AllOptions { get; set; } // List of all options (at time of form creation)
public ICollection<TestOption> Options { get; set; } // The options on a unit
public ICollection<TestFormSection> Sections { get; set; }
}
public FormViewModel Create(FormViewModel vm)
{
using (var context = new DataContext())
{
List<string> optionListStrings = GetOptionListForModelNumber(vm.MaterialNumber); // returns list of option codes
List<TestOption> matchingOptions = context.TestOptions
.Where(optionInDb =>
optionListStrings.Any(trimOption => trimOption == optionInDb.OptionCode)).ToList();
var unitType = context.UnitTypes.FirstOrDefault(x => x.Name == vm.UnitType);
string unitTypeId = unitType.Id;
TestForm2 newForm = new TestForm2
{
// ID & CreatedAt instantiated by Base Entity constructor
SerialNumber = vm.SerialNumber,
MaterialNumber = vm.MaterialNumber,
UnitTypeId = unitType.Id,
UsesStandardOptions = vm.UsesStandardOptions,
OptionsVerified = vm.OptionsVerified,
//AllOptions = context.TestOptions.ToList(),
//Options = matchingOptions,
Sections = vm.Sections,
};
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
context.TestForm2s.Add(newForm);
context.SaveChanges(); // THIS IS WHERE THE SQL EXCEPTION IS HAPPENING
return vm;
}
return null;
}
Lastly, I'm not sure if it's relevant, but a full copy of the related UnitType is viewable as part of newForm only after context.TestForm2s.add(newForm) resolves. This is weird to me since I don't think it should be automatically relating the data object like that.
I haven't been able to try much since everything looks properly configured to me. Please let me know if this is not the case or if I should include any other info.
Found the issue. The vm.Sections was not using viewmodels to contain the section data, so the vm.Sections contained UnitType database models. Since this was instantiated in the controller (before opening the data context in the TestForm2 Create method) EF assumed that these data were new and needed to be added to the UnitType table.
Hope this thread helps someone else running into similar issues.

Data not being saved on the second loop

I am trying to input data using a loop. In the first loop it is able to succesfully input the data. In the second loop when it comes to saving the data it comes up with an error message
An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll
but was not handled in user code:
'The property 'userTask.TaskScheduleId' is part of a key and so cannot be modified or marked as modified.
To change the principal of an existing
entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then
associate the dependent with the new principal.'
Here is the code below.
It creates a copy of the data in taskSchedule and then copies it to all the other users.
taskSchedule table has a many to many relationship with the users table which is why it is also saving data in the userTask table.
foreach(int user in users){
TaskSchedule copyTaskSchedule = new TaskSchedule();
copyTaskSchedule = taskSchedule;
copyTaskSchedule.Id = 0;
copyTaskSchedule.Notes = null;
copyTaskSchedule.UserTasks = null;
_context.TaskSchedules.Add(copyTaskSchedule);
_context.SaveChanges(); // this is where the code stops on the 2nd loop
// add updated user to the task
userTask copyUserTask = new userTask();
copyUserTask.TaskScheduleId = copyTaskSchedule.Id;
copyUserTask.UserId = user;
_context.userTasks.Add(copyUserTask);
_context.SaveChanges();
}
TaskSchedule model
public class TaskSchedule
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public bool isClosed { get; set; }
public bool isDeleted { get; set; }
public byte priorityLevel { get; set; }
public bool hasTimeLimit { get; set; }
public Customer customer { get; set; }
public int? customerId { get; set; }
public List<Note> Notes { get; set; }
public List<AttachmentFile> Attachments { get; set; }
public List<userTask> UserTasks {get; set;}
public int? userLastEditId { get; set; }
[ForeignKey("userLastEditId")]
public User userLastEdit { get; set; }
public DateTime? userLastEditDate { get; set; }
public DateTime taskCreatedDate { get; set; }
}
looks like copyTaskSchedule.Id is a primary key or a foreign key . You are initializing the value to copyTaskSchedule.Id = 0;
I think you must change this code
copyUserTask.TaskScheduleId = copyTaskSchedule.Id;
to this
copyUserTask.TaskSchedule = copyTaskSchedule;
I removed the code below
copyTaskSchedule = taskSchedule;
Then replaced it with the following code below. I manually added each field from taskSchedule to copyTaskSchedule
copyTaskSchedule.Title = taskSchedule.Title;
copyTaskSchedule.Start = taskSchedule.Start;
copyTaskSchedule.End = taskSchedule.End;
copyTaskSchedule.userLastEditId = taskSchedule.userLastEditId;
copyTaskSchedule.priorityLevel = taskSchedule.priorityLevel;
copyTaskSchedule.isClosed = taskSchedule.isClosed;
copyTaskSchedule.hasTimeLimit = taskSchedule.hasTimeLimit;
copyTaskSchedule.Attachments = taskSchedule.Attachments;
copyTaskSchedule.customerId = taskSchedule.customerId;
copyTaskSchedule.userLastEditDate = NowDateTime;
copyTaskSchedule.isDeleted = taskSchedule.isDeleted;
copyTaskSchedule.taskCreatedDate = NowDateTime;

WPF Entity Framework Foreign key won't load it's value in a DataGrid

I am using Entity Framework code first with fluent API I have an items table with foreign keys from users and units tables
but when I load the table to ObservableCollection then bind it to a datagrid the table normal column load it's data normally into the datagrid excpet for the foreign keys which show nothing but when i insert a break point to see the data inside the ObservableCollection I can see that every thing from Users and Units table is there
private void MainContentsWindow_ContentRendered(object sender, EventArgs e)
{
using (var db2 = new DataContext())
{
var AllItems2 = new ObservableCollection<Model.Items.Item>(db2.Items);
ItemsDataGrid.ItemsSource = AllItems2;
}
}
Users
public class User
{
public User()
{
Id = Guid.NewGuid();
IsActive = false;
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public UserGroup Group { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Items.Item> Items { get; set; } = new List<Items.Item>();
}
public enum UserGroup
{
Administrator = 1,
User,
Unknown
}
base
public class NormalBaseModel : CommonBase
{
public NormalBaseModel()
{
Id = new Guid();
CreateDate = DateTime.Now;
EditDate = null;
}
public Guid Id { get; set; }
public string Notes { get; set; }
public virtual User CreateBy { get; set; }
public DateTimeOffset? CreateDate { get; set; }
public virtual User EditBy { get; set; }
public DateTimeOffset? EditDate { get; set; }
}
items
public class Item : NormalBaseModel
{
public string NameAr { get; set; }
public string NameEn { get; set; }
public int? ManualId { get; set; }
public string Barcode { get; set; }
public byte?[] Image { get; set; }
public virtual Unit Unit { get; set; }
public string MadeIn { get; set; }
public bool IsSerail { get; set; }
public bool IsExpire{ get; set; }
}
Here is a test project on Github
https://github.com/ahmedpiosol/psychic-parakeet.git
https://imgur.com/a/zimd4
When you load your items via EF it needs to create new instances of User and Item. Behind the scenes, EF will call the constructor for each new instance. Your problem is in your constructors:
public User()
{
Id = Guid.NewGuid(); // <- here
}
Your constructor reassigns a new ID each time an instance is created, this will break the referential integrity and cause all sorts of other problems.
Your code doesn't know the difference between creating a new User and recreating a User instance from the database.
I suggest removing the assignments from inside your constructor and placing this either in a static Create method or place wherever you are creating a new User or Item.
p.s. WPF is irrelevant to your problem here.
Fluent API needs to specify foreign key in code, something like
modelBuilder.Entity<Items>()
.HasRequired(o => o.User)
.WithMany(c => c.Items)
.HasForeignKey(o => o.UserId);

Creating Object in EF which is null after saveChanges in DB

after i set the AuditReport on my Audit and save it, (in Debugger it is filled with a Proxy) there is still no Entry in the Database and I have no Idea why. Here are the relevant classes:
public class AuditReport
{
[Key]
[ForeignKey("Audit")]
[Column("AuditReport_ID")]
public int ID { get; set; }
[Required]
public virtual Audit Audit { get; set; }
}
public class Audit
{
[Key]
public int GeneratedID { get; set; }
[Index("Audit_ID", IsUnique = true)]
public int Audit_ID { get; set; }
public virtual AuditReport AuditReport { get; set; }
}
And the method in that the new AuditReport is Created
public async override Task SaveChangesAsync()
{
using (var dbAccess = new DatabaseAccess())
{
var foundAudit = dbAccess.Audits.Include("AuditReport").Include("AuditReport.Stellungnahmen").SingleOrDefault(_ => _.Audit_ID == Audit.Audit_ID);
if (foundAudit != null)
{
if (foundAudit.AuditReport == null)
{
foundAudit.AuditReport = dbAccess.AuditReports.Create();
foundAudit.AuditReport.Audit = foundAudit;
}
else
foundAudit.AuditReport.Stellungnahmen.ToList().ForEach(_ => dbAccess.Entry(_).State = EntityState.Deleted);
foreach (var item in Stellungnahmen.Where(_ => _.IsChecked == true))
foundAudit.AuditReport.Stellungnahmen.Add(dbAccess.Stellungnahmen.SingleOrDefault(_ => _.KeyWord == item.KeyWord));
}
await dbAccess.SaveChangesAsync();
}
}
As i already said, I've already debugged it and everything looks fine.
Try to remove [Key] on ID since you already have [ForeignKey] atrribute.

EF6 Interceptor to set a value on Insert or Update

I am having troubles trying to figure out how to use the EF6 interceptors to set a value on Insert/Update.
What I wanted to do is to have an interceptor to automatically create a new instance of Audit like so:
public class FooContext : DbContext
{
public DbSet<Invoice> Invoices { get; set; }
public DbSet<Audit> Audits { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public string Name { get; set; }
public Audit AuditAndConcurrencyKey { get; set; }
}
public class InvoiceItem
{
public int Id { get; set; }
public Invoice Header { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
//For legacy reasons. I know this design is wrong :(
public Audit AuditAndConcurrencyKey { get; set; }
}
public class Audit
{
public int Id { get; set; }
public int InstanceId { get; set; }
public string Message { get; set; }
}
[Test]
public void WillCreateAudit()
{
using (var db = new FooContext())
{
var inv = new Invoice {Name = "Foo Invoice"};
var invLine = new InvoiceItem {Header = inv, Price = 1, Name = "Apple"};
db.Invoices.Add(inv);
db.SaveChanges();
//Inceptors should figure out that we are working with "Invoice" and "InvoiceLine"
//And automatically create an "Audit" instance
Assert.That(inv.AuditAndConcurrencyKey != null);
Assert.That(invLine.AuditAndConcurrencyKey != null);
Assert.That(inv.AuditAndConcurrencyKey == invLine.AuditAndConcurrencyKey)
}
}
The first thing I checked is this example for SoftDeleteInterceptor. I don't think this is what I want because it looks like at the point where we are already generating the expression tree, we are no longer aware of the type of object you are working with.
I checked this example as well, but again, it looks like we are injecting strings instead of setting object references.
Ideally I want something like this:
public class AuditInterceptor
{
public void Intercept(object obj)
{
if (!(obj is Invoice) && !(obj is InvoiceItem))
return; //not type we are looking for, by-pass
//Set the audit here
}
}

Categories