I try to add entity through the navigation property of collection, but the following message comes up:
"Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions."
The models are:
SuggestionGroupDb:
public class SuggestionGroupDb
{
public Guid Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual TeguUserDb User { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage = "Required")]
[StringLength(30, MinimumLength = 1, ErrorMessage = "Invalid")]
public string Name { get; set; }
public int OrderNo { get; set; }
public virtual ICollection<SuggestionItemDb> Items { get; set; }
}
SuggestionItemDb:
public class SuggestionItemDb
{
public Guid Id { get; set; }
[Required]
public Guid SuggestionGroupId { get; set; }
[ForeignKey("SuggestionGroupId")]
public virtual SuggestionGroupDb SuggestionGroup { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage = "Required")]
[StringLength(30, MinimumLength = 1, ErrorMessage = "Invalid")]
public string Name { get; set; }
public int OrderNo { get; set; }
}
SuggestionGroup Repository Update function (simplified):
public async Task<SuggestionGroupRepositoryResult> UpdateAsync(string userid, SuggestionGroupDb suggestiongroup)
{
// Step 01 - Get the Entity
var dbSuggestionGroup = await GetAsync(userid, suggestiongroup.Id, suggestiongroup.Name);
// Step 02 - Update the items (just add one now)
foreach (var item in suggestiongroup.Items)
{
var sidb = new SuggestionItemDb() {Id = item.Id, Name = item.Name, OrderNo = item.OrderNo, SuggestionGroupId = item.SuggestionGroupId};
dbSuggestionGroup .Items.Add(sidb);
}
// Step 03 - Update the changes
try
{
var updated = context.AccSuggestionGroups.Update(dbSuggestionGroup);
await context.SaveChangesAsync();
return new SuggestionGroupRepositoryResult("Valid") /*{SuggestionGroup = updated.Entity}*/;
}
catch (Exception e)
{
context.Reset();
return new SuggestionGroupRepositoryResult("Failed", e.Message);
}
}
The problem is that SaveChanges throws and exception with the given message.
Is it possible to update the SuggestionItems through the SuggestionGroup?
I am using EF Core 3.0 preview 6.
Prior to EF Core 3.0, untracked entities discovered by the DetectChanges strategy (in your case, by adding an untracked entity to a collection) would automatically be in the Added state.
This is no longer the case. From Entity Framework Core 3.0 the entity will be automatically added in the Modified state.
Why
This change was made to make it easier and more consistent to work with disconnected entity graphs while using store-generated keys.
Source: EF Core 3.0 - Breaking Changes
You can force new untracked entities to be added in the Added state by configuring the key property to explicitly not use generated values.
For example:
public class SuggestionItemDb
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
Or using the fluent API
modelBuilder
.Entity<SuggestionItemDb>()
.Property(e => e.Id)
.ValueGeneratedNever();
Related
My first question - be kind :-).
In the code below, I am attempting to reference an "Include(d)" entity (Schedules) to obtain its Name property. In EF 6, both "schedule" and "schedule_2" return the correct value of Name. In EF Core, "schedule" returns NULL and "schedule_2" returns the correct value of Name.
I do not understand why I should have to load the "schedules" List. Shouldn't the .Include force an Eager Load of the Schedules for each Election such that each Election Schedule's Name property would be available for the "schedule" assignment?
// Relevant Model entities in database
// DbSet<Election> Elections { get; set; }
//
// The following are the related classes defined in the database context...
public class Election
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Schedule> Schedules { get; set; }
}
public class Schedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int? CfsElectionId { get; set; }
public string Name { get; set; }
[Required] // sets cascade delete
[ForeignKey("CFSElectionID")]
public virtual Election Election { get; set; }
}
class Program
{
static void Main()
{
var db = new FfmsDbContext();
var elections = db.Elections
.Include(i => i.Schedules)
.ToList();
//The following returns NULL?
var schedule = elections.First().Schedules?.First().Name ?? "NULL";
var schedules = db.Schedules
.ToList();
//The following returns the correct Name property?
var schedule_2 = elections.First().Schedules?.First().Name ?? "NULL";
Console.WriteLine($#"sched: {schedule}");
Console.WriteLine($#"schedules.First().Name: {schedules.First().Name}");
Console.WriteLine($#"sched2: {schedule_2}");
Console.WriteLine("Done...");
Console.ReadLine();
}
}
/*
Output...
sched: NULL
schedules.First().Name: Candidates
sched2: Candidates
Done...
*/
Turns out that my problem ended up being in the References of the Class.
I had accidentally chosen System.Data.Entity as the offered choice for .Include.
The correct reference should have been Microsoft.EntityFrameworkCore.
Once I adjusted the reference, the .Include worked as desired.
I have an Entity Framework model:
public class Application
{
[Key]
public int ApplicationID { get; set; }
public int PatentID { get; set; }
...
//------------------------------------
public string ApplicationNumber { get; set; }
public string Priority { get; set; }
public List<ApplicationPayment> Payments { get; set; }
= new List<ApplicationPayment>();
}
and payment's model:
public class ApplicationPayment
{
[Key]
public int PaymentID { get; set; }
public string PaymentName { get; set; }
public float Amount { get; set; }
public int PayNumber { get; set; }
public DateTime Date { get; set; } = new DateTime(2017, 12, 1);
public float TopicPart { get; set; }
}
Entity Framework creates additional foreign keys for me in ApplicationPayment model Application_ApplicationID.
I add a new instance in ApplicationPayment table that has number of the existing Application:
But when I try to display this ApplicationPayment's table this returns the empty table.
I tried to add ApplicationPayment manually through SQL Server Management Studio and via fake-request. New line added but the list of ApplicationPayment is still empty.
Fake-request:
[HttpPut]
public void CreateApplicationPayment(int? id)
{
ApplicationPayment appPayment = new ApplicationPayment()
{
Amount = 80.0f,
Date = new DateTime(2017, 10, 25),
PaymentName = "payment",
PayNumber = 30,
TopicPart = 20
};
Application application = db.Applications.Find(id);
application.Payments.Add(appPayment);
db.SaveChanges();
}
Your collection property needs to be virtual if you want EF to automatically populate it:
public virtual List<ApplicationPayment> Payments { get; set; }
Also, if you're using EF 6 or previous, you'll need to make the type of that property ICollection<ApplicationPayment>, rather than List<ApplicationPayment>. I think EF Core relaxed this restriction, but I'm not sure. So, if you still have issues, change it there as well.
However, this is what's called lazy-loading, and it's not ideal in most scenarios. Additionally, if you're using EF Core, it still won't work, because currently EF Core does not support lazy loading. The better method is to eagerly load the relationship. This is done by using Include in your LINQ query:
Application application = db.Applications.Include(m => m.ApplicationPayments).SingleOrDefault(m => m.Id == id);
This will cause EF to do a join to bring in the related ApplicationPayments. You need to then use SingleOrDefault rather than Find, as Find doesn't work with Include. (Find looks up the object in the context first, before hitting the database, and as a result, cannot account for related items being available.)
Sorry for my bad English.
I have EF 6 codefirst models for database.
3 models is usually static, loaded at program startup (using aka foreign keys for last table).
Last model is dynamical - data is loaded too, stored in c# collection, but user can add, edit rows and save added/edited to DB.
For 3 first models i have checkboxes with selecteditem binding.
User can edit last table model entity, select items from checkboxes and save to DB.
This is simple and standart solution.
Partial models without trash fields (to reference from last table).
[Table("Users")]
public partial class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ClientId { get; set; }
[StringLength(160)]
public string ClientName { get; set; }
public virtual ICollection<Repair> Repairs { get; set; }
public User()
{
Repairs = new List<Repair>();
}
}
[Table("RepairStatuses")]
public partial class RepairStatus
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(10)]
public string Status { get; set; }
public virtual ICollection<Repair> Repairs { get; set; }
public RepairStatus()
{
Repairs = new List<Repair>();
}
}
[Table("CurrentStatuses")]
public partial class CurrentStatus
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StatusId { get; set; }
[StringLength(10)]
public string Status { get; set; }
public virtual ICollection<Repair> Repairs { get; set; }
public CurrentStatus()
{
Repairs = new List<Repair>();
}
}
And main editable table model (partial too w/o trash fields).
[Table("Repairs")]
public partial class Repair
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Column(TypeName = "date")]
public DateTime Date { get; set; }
[StringLength(255)]
public string HardwareInfo { get; set; }
public virtual User User { get; set; }
public virtual RepairStatus RepairStatus { get; set; }
public virtual CurrentStatus CurrentStatus { get; set; }
}
In my AddEntity method all working (attach unchanged items from combobox to DbContext, add new row, save changes). Eager loading.
using (ServiceDBContext cntx = new ServiceDBContext())
{
cntx.Users.Attach(SelectedRepair.User);
cntx.CurrentStatuses.Attach(SelectedRepair.CurrentStatus);
cntx.RepairStatuses.Attach(SelectedRepair.RepairStatus);
cntx.Entry(SelectedRepair.RepairStatus).State = EntityState.Modified;
cntx.Entry(SelectedRepair.CurrentStatus).State = EntityState.Modified;
cntx.Entry(SelectedRepair.User).State = EntityState.Modified;
cntx.Repairs.Attach(SelectedRepair);
cntx.Entry(SelectedRepair).State = EntityState.Added;
...
cntx.SaveChanges();
...
But with EditEntity method i have strange behavior (sorry for stupid code...)
using (ServiceDBContext wrk = new ServiceDBContext())
{
var tmp = (((((wrk.Repairs.Where(x => x.Id ==SelectedRepair.Id)).Include(y => y.CurrentStatus)).Include(y => y.RepairStatus)).Include(y => y.Engineer)).Include(y => y.User)).FirstOrDefault();
if (tmp.User.ClientId != SelectedRepair.User.ClientId)
{
tmp.User = SelectedRepair.User;
wrk.Users.Attach(tmp.User);
wrk.Entry(tmp.User).State = EntityState.Modified;
}
if (tmp.RepairStatus.Id != SelectedRepair.RepairStatus.Id)
{
tmp.RepairStatus = SelectedRepair.RepairStatus;
wrk.RepairStatuses.Attach(tmp.RepairStatus);
wrk.Entry(tmp.RepairStatus).State = EntityState.Modified;
}
if (tmp.CurrentStatus.StatusId != SelectedRepair.CurrentStatus.StatusId)
{
tmp.CurrentStatus = SelectedRepair.CurrentStatus;
wrk.CurrentStatuses.Attach(tmp.CurrentStatus);
wrk.Entry(tmp.CurrentStatus).State = EntityState.Modified;
}
...
wrk.Entry(tmp).State = EntityState.Modified;
wrk.SaveChanges();
}
For example: CurrentStatuses table have 2 entities ("1. OK", "2. Bad").
Then user first time changing in Repair table in selected row CurrentStatus foreign key (for example, with id =1 to foreign key with id=2) all is OK.
In VS debugger i can see...
UPDATE [dbo].[CurrentStatuses] SET [Status] = #0 WHERE ([StatusId] = #1)
UPDATE [dbo].[Repairs] SET ... WHERE (([Id] = #12) AND ([CurrentStatus_StatusId] = #13))
If user want to change second time this entity from id=2 to id=1 (reverse) its throwing error "An error occurred while saving entities that do not expose foreign key properties for their relationships..."
AND in debugger we can see some magic with "Reader (INSERT)" attempts to all database relative tables o_O and attempt to INSERT in Repair table dublicate entry (which was been selected to edit).
One INSERT example (Repair, RepairStatus and User have like this INSERTS too):
DECLARE #0 AS SQL_VARIANT;
SET #0 = NULL;
INSERT [dbo].[CurrentStatuses]([Status])
VALUES (#0)
SELECT [StatusId]
FROM [dbo].[CurrentStatuses]
WHERE ##ROWCOUNT > 0 AND [StatusId] = scope_identity()
After program restart we can change CurrentStatus foreign key from id=2 to id=1 normally (but only 1 time too).
Can someone help me to solve this problem?
Thanks!
I have an existing database I'm coding against with Entity Framework 6 Code First. I have a many-to-many relationship that is working for Selects, Inserts and Deletes. I'm having an issue with EF adding an extra insert to the many-to-many table for an existing relationship.
Schema:
DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MainContext>(null);
modelBuilder.Entity<DocumentType>()
.HasMany(u => u.DocumentStatuses)
.WithMany()
.Map(m =>
{
m.MapLeftKey("DOCUMENT_TYPE_ID");
m.MapRightKey("DOCUMENT_STATUS_ID");
m.ToTable("DOCUMENT_TYPES_DOCUMENT_STATUSES");
});
base.OnModelCreating(modelBuilder);
}
DTOs:
[Table("DOCUMENT_TYPES")]
public class DocumentType
{
[Key]
[Column("DOCUMENT_TYPE_ID")]
public int? Id { get; set; }
[Required]
[Column("TYPE_NAME")]
public string TypeName { get; set; }
[Required]
[Column("IS_ACTIVE")]
public bool IsActive { get; set; }
[Display(Name = "Updated By")]
[Column("LAST_UPDATED_BY")]
public string LastUpdatedBy { get; set; }
[Display(Name = "Updated Date")]
[Column("LAST_UPDATED_DATE")]
public DateTimeOffset? LastUpdatedDate { get; set; }
public virtual List<DocumentStatus> DocumentStatuses { get; set; }
public DocumentType()
{
DocumentStatuses = new List<DocumentStatus>();
}
}
[Table("DOCUMENT_STATUSES")]
public class DocumentStatus
{
[Key]
[Column("DOCUMENT_STATUS_ID")]
public int? Id { get; set; }
[Required]
[Column("STATUS_NAME")]
public string StatusName { get; set; }
[Required]
[Column("IS_COMPLETE")]
public bool IsComplete { get; set; }
[Required]
[Column("IS_ACTIVE")]
public bool IsActive { get; set; }
[Display(Name = "Updated By")]
[Column("LAST_UPDATED_BY")]
public string LastUpdatedBy { get; set; }
[Display(Name = "Updated Date")]
[Column("LAST_UPDATED_DATE")]
public DateTimeOffset? LastUpdatedDate { get; set; }
}
Repository Update:
public bool Update(DocumentType entity, string updatedBy)
{
DateTimeOffset updatedDateTime = DateTimeOffset.Now;
entity.LastUpdatedBy = updatedBy;
entity.LastUpdatedDate = updatedDateTime;
using (var db = new MainContext())
{
db.DocumentTypes.Add((DocumentType)entity);
db.Entry(entity).State = EntityState.Modified;
foreach (var item in entity.DocumentStatuses)
{
if (item.Id != null)
db.Entry(item).State = EntityState.Unchanged;
}
db.SaveChanges();
}
return true;
}
The loop that contains:
if (item.Id != null)
db.Entry(item).State = EntityState.Unchanged;
Prevents new records from being added, but EF still tries to insert into DOCUMENT_TYPES_DOCUMENT_STATUSES a duplicate of the existing many-to-many records.
Unit Test:
[TestMethod()]
public void UpdateTest()
{
DocumentTypeRepository documentTypeRepository = new DocumentTypeRepository();
DocumentType type = NewDocumentType(true);
DocumentType typefromDb;
string updatedBy = "DocumentTypeRepositoryTests.UpdateTest";
bool actual;
type.IsActive = true;
type.TypeName = RandomValues.RandomString(18);
type.DocumentStatuses.Add(DocumentStatusRepositoryTests.NewDocumentStatus(true));
actual = documentTypeRepository.Update(type, updatedBy);
Assert.AreEqual(true, actual);
typefromDb = documentTypeRepository.GetById((int)type.Id);
Assert.AreEqual(type.DocumentStatuses.Count, typefromDb.DocumentStatuses.Count);
}
How can I set the many-to-many table to EntityState.Unchanged when it already exists?
Try replacing
db.DocumentTypes.Add((DocumentType)entity);
db.Entry(entity).State = EntityState.Modified;
with
db.Entry(entity).State = entity.Id == null
? EntityState.Added
: EntityState.Modified;
When doing CRUD operations with detached entities, it's easier not to use the DbSet operations and only manipulate entity state entries. At least, it leads to less errors.
Two things are important to note here:
When you Add an entity to a context, the whole object graph owned by the entity is marked as Added.
In any association this also marks the associations as new. However, while in a 1:n or 1:1 association the state of the association changes when the state of one of the ends changes, in many-to-many associations there is a part of the association that is hidden and that will always remain Added. (Under the hood a many-to-many association is a 1:n:1 association, the n part is not visible in your code).
So this statement...
db.DocumentTypes.Add(entity);
causes the associations to get the Added state, and the subsequent...
db.Entry(entity).State = EntityState.Modified;
doesn't change this any more.
You have to make sure that for an existing DocumentType the state is never changed to Added, which you can do by checking its Id values, same as you do for DocumentStatus.
Also, you have to change the way you set the state of DocumentStatuses:
foreach (var item in entity.DocumentStatuses)
{
db.Entry(item).State = item.Id != null
? EntityState.Unchanged
: EntityState.Added;
}
I am using VS 2010 with Entity Framework 5 code first and C# and have a web application (hence disconnected entities). I am used to working with SQL queries directly but am very new to EF and code first.
I have two classes:
public class User
{
public int UserID {get; set;}
public string UserName { get; set; }
public bool IsSuspended { get; set; }
public int UnitID { get; set; }
public virtual MyTrust MyTrusts { get; set; }
}
public class MyTrust
{
public int MyTrustID { get; set; }
public string MyTrustName { get; set; }
public string Region { get; set; }
public bool DoNotUse { get; set; }
}
and my DbContext class contains:
public DbSet<MyTrust> MyTrust { get; set; }
public DbSet<User> Users { get; set; }
modelBuilder.Entity<User>()
.HasRequired(m => m.MyTrust);
The MyTrust entity will not be changed
There are three scenarios I am interested in:
Adding a user with an existing MyTrust
Updating a user with no change to the trust
Updating a user with a change to the trust
When the website returns the data the MyTrust object has only the MyTrustID set. When I update/add the user the MyTrust record is also updated.
CLARIFICATION The relationship in the User object is NOT updated; the actual MyTrust object is updated with the data returned from the website; as most fields are empty this is corrupting the object AND not achieving the required update of the User record.
In fact, the problem seems to boil down to the fact that the wrong end of the relationship is being updated.
I have looked at some many examples I cannot see a simple solution.
Can anyone please suggest a straightforward pattern for this (it was so easy in the SQL days).
UPDATE
I resolved this by adding specific keys to the User and MyTrust classes.
public int NHSTrustID { get; set; }
and a matching key in the MyTrust class.
In retrospect the question was wrong. I wasn't after patterns but the solution to a specific problem.
I've given some examples below - I've done them from memory but hopefully will give you a good starting point:
Adding a user with an existing MyTrust
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Added
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.Entry(myUser.MyTrusts).Property(x => x.MyTrustName).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.Region).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.DoNotUse).IsModified = false;
context.SaveChanges();
}
Updating a user with no change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Unchanged;
context.SaveChanges();
}
Updating a user with a change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.SaveChanges();
}