Entity Framework - DbContext SaveChanges() - entity-framework

Can someone tell me is it possible, and if it is how to avoid using two times _context.SaveChanges() in this code?
Message m = new Message
{
Title = message.Title,
Body = message.Body,
Date = DateTime.Now
};
_context.Messages.Add(m);
_context.SaveChanges();
UserMessage messageToUser = new UserMessage
{
MessageID = m.ID,
ProductID = message.ProductID,
SenderID = message.SenderID,
RecieverID = reciever.Id
};
_context.UserMessages.Add(messageToUser);
_context.SaveChanges();
This is how my Entities look like
public class UserMessage
{
public int ID { get; set; }
public string SenderID { get; set; }
public string RecieverID { get; set; }
public int? ProductID { get; set; }
public int MessageID { get; set; }
public User Sender { get; set; }
public User Reciever { get; set; }
public Product Product { get; set; }
public Message Message { get; set; }
}
public class Message
{
public int ID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime Date { get; set; }
}

On your UserMessage class, you set the reference instead of the foreign key, as the foreign key is not known yet.
In your code, that would mean:
Message m = new Message
{
Title = message.Title,
Body = message.Body,
Date = DateTime.Now
};
_context.Messages.Add(m);
UserMessage messageToUser = new UserMessage
{
ProductID = message.ProductID,
SenderID = message.SenderID,
RecieverID = reciever.Id,
Message = m
};
_context.UserMessages.Add(messageToUser);
_context.SaveChanges();

Related

Entity Framwork Update many to many

I am new to ef core. I am trying to implement many to many .
Here is my DBContext
public class MaxCiSDbContext : DbContext
{
public DbSet<Job> Jobs { get; set; }
public DbSet<Staff> Staffs { get; set; }
public MaxCiSDbContext(DbContextOptions<MaxCiSDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Job>()
.HasMany(t => t.Staffs)
.WithMany(t => t.Jobs);
base.OnModelCreating(modelBuilder);
}
}
and Here is my Staff Class
public class Staff
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public string Address { get; set; }
//Navigation
public virtual ICollection<Job> Jobs { get; set; }
}
Here is my Job Class
public class Job
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string State { get; set; }
public string ClientOrderNumber { get; set; }
public string StartDate { get; set; }
public string DueDate { get; set; }
public string CompletedDate { get; set; }
public string ClientId { get; set; }
public string ManagerId { get; set; }
public string PartnerId { get; set; }
//Navigation
public virtual ICollection <Staff> Staffs { get; set; }
}
I Call an API which returns a XmlDocument, I read that document and update database.
Here is how I deal with xmldocument.
//Fetch Current Jobs and populate to DB
XmlDocument apiresults = JobMethods.GetCurrent();
XmlNodeList nodes = apiresults.DocumentElement.SelectNodes("/Response/Jobs/Job");
foreach (XmlNode node in nodes)
{
Job MaxCiSJob = new Job()
{
Id = node.SelectSingleNode("ID").InnerText,
Name = node.SelectSingleNode("Name").InnerText,
Description = node.SelectSingleNode("Description").InnerText,
State = node.SelectSingleNode("State").InnerText,
ClientOrderNumber = node.SelectSingleNode("ClientOrderNumber").InnerText,
StartDate = node.SelectSingleNode("StartDate").InnerText,
DueDate = node.SelectSingleNode("DueDate") != null ? node.SelectSingleNode("DueDate").InnerText : "",
CompletedDate = node.SelectSingleNode("CompletedDate") != null ? node.SelectSingleNode("CompletedDate").InnerText : "",
ClientId = node.SelectSingleNode("Client/ID").InnerText,
ManagerId = node.SelectSingleNode("Manager/ID") != null ? node.SelectSingleNode("Manager/ID").InnerText : "",
PartnerId = node.SelectSingleNode("Partner") != null ? node.SelectSingleNode("Partner").InnerText : ""
};
XmlNodeList Assigned = node.SelectNodes("Assigned/Staff");
MaxCiSJob.Staffs = new Collection<Staff>();
foreach (XmlNode staffNode in Assigned)
{
var staffId = staffNode.SelectSingleNode("ID").InnerText;
var staff = _db.Staffs.Find(staffId);
if(staff != null)
{
MaxCiSJob.Staffs.Add(staff);
}
}
if (_db.Jobs.Find(MaxCiSJob.Id) == null)
{
//Insert Record
_db.Jobs.Add(MaxCiSJob);
}
else
{
// UPDATE recorde
_db.Jobs.Update(MaxCiSJob);
}
}
_db.SaveChanges();
}
Everything works well when I run the program for the first time(The linking table ,"JobStaff", is empty) but when I run the Program for the second time I get an excetpion:
SqlException: Violation of PRIMARY KEY constraint 'PK_JobStaff'. Cannot insert duplicate key in object 'dbo.JobStaff'. The duplicate key value is (J14995, 557898).
Can someone please help me on how can I resolve this issue.
Running your code EF core wants to add entities anyway. Because your entities are not attached.
Try this code:
//Fetch Current Jobs and populate to DB
XmlDocument apiresults = JobMethods.GetCurrent();
XmlNodeList nodes = apiresults.DocumentElement.SelectNodes("/Response/Jobs/Job");
foreach (XmlNode node in nodes)
{
var id = node.SelectSingleNode("ID").InnerText;
Job MaxCiSJob = _db.Jobs.Find(id);
if (MaxCiSJob == null)
{
MaxCiSJob = new Job() { Id = id };
_db.Jobs.Add(MaxCiSJob);
}
MaxCiSJob.Name = node.SelectSingleNode("Name").InnerText;
MaxCiSJob.Description = node.SelectSingleNode("Description").InnerText;
MaxCiSJob.State = node.SelectSingleNode("State").InnerText;
MaxCiSJob.ClientOrderNumber = node.SelectSingleNode("ClientOrderNumber").InnerText;
MaxCiSJob.StartDate = node.SelectSingleNode("StartDate").InnerText;
MaxCiSJob.DueDate = node.SelectSingleNode("DueDate") != null ? node.SelectSingleNode("DueDate").InnerText : "";
MaxCiSJob.CompletedDate = node.SelectSingleNode("CompletedDate") != null ? node.SelectSingleNode("CompletedDate").InnerText : "";
MaxCiSJob.ClientId = node.SelectSingleNode("Client/ID").InnerText;
MaxCiSJob.ManagerId = node.SelectSingleNode("Manager/ID") != null ? node.SelectSingleNode("Manager/ID").InnerText : "";
MaxCiSJob.PartnerId = node.SelectSingleNode("Partner") != null ? node.SelectSingleNode("Partner").InnerText : "";
XmlNodeList Assigned = node.SelectNodes("Assigned/Staff");
foreach (XmlNode staffNode in Assigned)
{
MaxCiSJob.Staffs.Clear();
var staffId = staffNode.SelectSingleNode("ID").InnerText;
var staff = _db.Staffs.Find(staffId);
if (staff != null)
{
MaxCiSJob.Staffs.Add(staff);
}
}
}
_db.SaveChanges();
And you should change your domains this way in order not to get NullReferenceException:
public class Staff
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public string Address { get; set; }
//Navigation
public virtual ICollection<Job> Jobs { get; set; } = new Collection<Job>();
}
public class Job
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string State { get; set; }
public string ClientOrderNumber { get; set; }
public string StartDate { get; set; }
public string DueDate { get; set; }
public string CompletedDate { get; set; }
public string ClientId { get; set; }
public string ManagerId { get; set; }
public string PartnerId { get; set; }
//Navigation
public virtual ICollection<Staff> Staffs { get; set; } = new Collection<Staff>();
}

Entity Framework .Core treating foreign key as a primary key

I have the two classes shown below. When trying to insert two HeroEntry with the same IdentityId, the context is only saving one of the two entries. Any ideas what I am doing wrong?
public class Identity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int IdentityId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
[JsonIgnore]
public virtual HeroEntry HeroEntry { get; set; }
}
public class HeroEntry
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int HeroEntryId { get; set; }
public int IdentityId { get; set; }
public SuperHeroEnum HeroType { get; set; }
public DateTime Startdate { get; set; }
public DateTime EndDate { get; set; }
[ForeignKey("IdentityId")]
public virtual Identity Identity { get; set; }
public virtual List<SuperHeroBreak> SuperHeroBreaks { get; set; }
}
Add code:
var b = new HeroEntry()
{
HeroType = SuperHeroEnum.BATMAN,
Startdate = DateTime.Parse("12/24/2018"),
EndDate = DateTime.Parse("01/04/2019"),
IdentityId = 1,
};
var r = new HeroEntry()
{
HeroType = SuperHeroEnum.ROBIN,
Startdate = DateTime.Parse("12/24/2018"),
EndDate = DateTime.Parse("01/04/2019"),
IdentityId = 1,
};
context.SuperHeroes.AddRange(b, r);
var affected = context.SaveChanges();
Cristian was correct. Adding the navigation property to the identity class solved my issue. Thank you for the help!

The property 'Id' is part of the object's key information and cannot be modified after the second insert

I have a table with Id as primary key
The model generated by EF is this
public partial class Home_Orchard_Users_UserPartRecord
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string NormalizedUserName { get; set; }
public string Password { get; set; }
public string PasswordFormat { get; set; }
public string HashAlgorithm { get; set; }
public string PasswordSalt { get; set; }
public string RegistrationStatus { get; set; }
public string EmailStatus { get; set; }
public string EmailChallengeToken { get; set; }
public Nullable<System.DateTime> CreatedUtc { get; set; }
public Nullable<System.DateTime> LastLoginUtc { get; set; }
public Nullable<System.DateTime> LastLogoutUtc { get; set; }
}
When I tried to insert a data from a list, the first insert was okay but the second one I got error: The property 'Id' is part of the object's key information and cannot be modified.
foreach (var ContentItem in ListContentItem)
{
ContentItemData.Data = ContentItem.Data;
ContentItemData.ContentType_id = 3;
Context.Home_Orchard_Framework_ContentItemRecord.Add(ContentItemData);
Context.SaveChanges();
int Return_Id = ContentItemData.Id;
UserData.Id = Return_Id;
UserData.UserName = ContentItem.UserName;
UserData.Email = ContentItem.Email;
UserData.NormalizedUserName = ContentItem.NormalizedUserName;
UserData.Password = "AMQ6a4CzqeyLbWqrd7EwoaTV23fopf++3FpcBlV+Gsvgja3Ye3LwFlXVHyhAcWyKaw==";
UserData.PasswordFormat = "Hashed";
UserData.HashAlgorithm = "PBKDF2";
UserData.PasswordSalt = "FaED9QsIV9HD95m8FO5OSA==";
UserData.RegistrationStatus = "Approved";
UserData.EmailStatus = "Approved";
UserData.CreatedUtc = DateTime.Today;
Context.Home_Orchard_Users_UserPartRecord.Add(UserData);
//Context.SaveChanges();
i = i + 1;
progress = ((float)i / nCount) * 100;
Console.Write("\r{0}%", progress);
Thread.Sleep(50);
}
Why I can't insert the second time? The Id is the primary key and it's not autogenerated so it has to be input manually.

Adding new related entities in a single action

Every riddle has one or more questions, how can add both a Riddle and a Question to that riddle by submitting a single form?
This is RiddlesController Create action code:
public ActionResult Create(RiddleViewModel model)
{
if (ModelState.IsValid)
{
try
{
_db.Riddles.Add(new Models.Riddle
{
Name = model.Name,
Description = model.Description ,
CreationDate = DateTime.Now,
User = _db.Users.Find(User.Identity.GetUserId()),
});
_db.Questions.Add(new Models.Question
{
Body = model.FirstQuestionBody,
Answer = model.FirstQuestionAnswer,
CreationDate = DateTime.Now,
// What should I write here? or is there any better way to accomplish this?
Riddle = ?????
});
_db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
return View();
}
This is Riddle model code:
public class Riddle
{
public int Id { get; set; }
public string Name { get; set; }
[MaxLength(200)]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public List<Review> Reviews { get; set; }
[Required]
public ApplicationUser User { get; set; }
public virtual List<Question> Questions { get; set; }
[Column(TypeName = "datetime2")]
public DateTime CreationDate { get; set; }
}
This is Question model code:
public class Question
{
public int Id { get; set; }
public string Body { get; set; }
public string Answer { get; set; }
public Riddle Riddle { get; set; }
[Column(TypeName ="datetime2")]
public DateTime CreationDate { get; set; }
}
This is RiddleViewModel code:
public class RiddleViewModel
{
[Required]
public string Name { get; set; }
[MaxLength(200)]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
// Question properties
[DataType(DataType.MultilineText)]
public string FirstQuestionBody { get; set; }
public string FirstQuestionAnswer { get; set; }
}
You can try as shown below.
_db.Questions.Add(new Models.Question
{
Body = model.FirstQuestionBody,
Answer = model.FirstQuestionAnswer,
CreationDate = DateTime.Now,
Riddle = new Models.Riddle
{
Name = model.Name,
Description = model.Description ,
CreationDate = DateTime.Now,
User = _db.Users.Find(User.Identity.GetUserId()),
}
});
_db.SaveChanges();

How do I chain properties with Code First entity framework?

I'm trying to do what seems fairly simple but I'm getting a null reference....
I have a null on the assoc files property in the last statement...
TestInfo.AggregateRoutes.MainBlogEntry = new Blog { BlogType = 1, Title = TestInfo.UniqueRecordIdentifier, Description = TestInfo.UniqueRecordIdentifier, DateAdded = DateTime.Now, User = TestInfo.UniqueRecordIdentifier };
IBlogRepository blogRepo = new BlogRepository();
var assocFile = new AssocFile { Name = TestInfo.UniqueRecordIdentifier, Url = TestInfo.UniqueRecordIdentifier };
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles.Add(assocFile);
This is the code I have written to support what I'm trying to do...
public class PteDotNetContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<AssocFile> AssocFiles { get; set; }
}
public class Blog
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BlogId { get; set; }
public int BlogType { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public string User { get; set; }
public virtual ICollection<AssocFile> AssocFiles { get; set; }
}
public class AssocFile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AssocFileId { get; set; }
public int BlogId { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public virtual Category Category { get; set; }
}
I thought the whole point in declaring it virtual was that it would create a foreign key constraint?
When you instantiate an entity you also need to initialize the collection navigational properties before you access it for the first time. In your case MainBlogEntry.AssocFiles = new List<AssocFile>();. The reason for this is, your property implementation does not contain any logic to initialize the collection.
When EF creates new instances of your entities, it sub classes your entities (ie Proxy Creation) and over ride the default functionality of your properies.
TestInfo.AggregateRoutes.MainBlogEntry = new Blog { BlogType = 1, Title = TestInfo.UniqueRecordIdentifier, Description = TestInfo.UniqueRecordIdentifier, DateAdded = DateTime.Now, User = TestInfo.UniqueRecordIdentifier };
IBlogRepository blogRepo = new BlogRepository();
var assocFile = new AssocFile { Name = TestInfo.UniqueRecordIdentifier, Url = TestInfo.UniqueRecordIdentifier };
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles = new List<AssocFile>();
TestInfo.AggregateRoutes.MainBlogEntry.AssocFiles.Add(assocFile);