Recursive Entity Update - c#

I have an entity which holds a list of entities (same as root entity) to represent a Folder structure:
public class SopFolder
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastUpdated { get; set; }
public int Status { get; set; }
public virtual ICollection<SopField> SopFields { get; set; }
public virtual ICollection<SopFolder> SopFolderChildrens { get; set; }
public virtual ICollection<SopBlock> Blocks { get; set; }
public virtual ICollection<SopReview> Reviews { get; set; }
}
This entity is stored in my DB using Code-First Approach which is working fine. I then print the entity to a KendoUI Treeview, let the user modify it and on "save" post it back to the Server to an Action as IEnumerable<TreeViewItemModel> items.
I then look for the ROOT entity with all it's children (there is only one root) and convert it back into an SopFolder object.
To get the full object updated in the database I do the following:
List<SopFolder> sopfolderlist = ConvertTree(items.First());
SopFolder sopfolder = sopfolderlist[0];
if (ModelState.IsValid)
{
SopFolder startFolder = new SopFolder { Id = sopfolder.Id };
//db.SopFolders.Attach(startFolder);
// db.SopFolders.Attach(sopfolder);
startFolder.Name = sopfolder.Name;
startFolder.LastUpdated = sopfolder.LastUpdated;
startFolder.SopFields = sopfolder.SopFields;
startFolder.SopFolderChildrens = sopfolder.SopFolderChildrens;
startFolder.Status = sopfolder.Status;
db.Entry(startFolder).State = EntityState.Modified;
db.SaveChanges();
return Content("true");
}
However this is not working. The model is not updated at all. If I shift the "entityState.Modified" before the modifications, it just creates a complete fresh duplicate of my data in the database (modified of course).
Is my approach correct or do I have to go a different path? What am I missing here? I guess there is another "hidden" id which lets the EF map the entities to the db entries but I am not sure about this. Thanks for help!
UPDATE:
Instead of creatinga new instance of SopFolder I also tried db.SopFolders.Find(sopfolder.Id) and this works for entries with no children. If I have entities with children, it creates a duplicate.
Regards,
Marcus

This is typical Disconnected Graph scenario. Please see this question for possible solutions:
Disconnected Behavior of Entity Framework when Updating Object Graph
You have already figure out the first solution - that is: update entities separately. Actually, what you should do is to fetch the original data from database and then do comparison of what have changed. There are some generic ways of doing that, some of them are described in "Programming EF DbContext" book by J.Lerman, which I strongly recommend to you before doing more coding using EF.
P.S. IMHO this is the worse downside of EF.

Replace SopFolder startFolder = new SopFolder { Id = sopfolder.Id }; with
SopFolder startFolder = db.SopFolders.FirstOrDefault(s=>s.Id.Equals(sopfolder.Id));
// then validate if startFolder != null

I recommend you to create your entity model with ParentId, not children object list. When you need treeview model collect it with recursive function from database.
public class SopFolder
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastUpdated { get; set; }
public int Status { get; set; }
public virtual ICollection<SopField> SopFields { get; set; }
//public virtual ICollection<SopFolder> SopFolderChildrens { get; set; }
public int? ParentFolderId { get; set; }
public virtual ICollection<SopBlock> Blocks { get; set; }
public virtual ICollection<SopReview> Reviews { get; set; }
}
When you create children folders, select it's parent, so collect your data. In childrens case try this :
List<SopFolder> sopfolderlist = ConvertTree(items.First());
SopFolder sopfolder = sopfolderlist[0];
if (ModelState.IsValid)
{
SopFolder startFolder = new SopFolder { Id = sopfolder.Id };
//db.SopFolders.Attach(startFolder);
// db.SopFolders.Attach(sopfolder);
startFolder.Name = sopfolder.Name;
startFolder.LastUpdated = sopfolder.LastUpdated;
startFolder.SopFields = sopfolder.SopFields;
startFolder.SopFolderChildrens = sopfolder.SopFolderChildrens;
foreach (var child in sopfolder.SopFolderChildrens)
{
db.SopFolders.CurrentValues.SetValues(child);
db.SaveChanges();
}
startFolder.Status = sopfolder.Status;
db.Entry(startFolder).State = EntityState.Modified;
db.SaveChanges();
return Content("true");
}

Related

Don't save null image data if image was not re-uploaded Asp.net mvc

I have been following the SportsStore example project in Apress Pro ASP.NET MVC 3 Framework book and trying to apply the concepts to my application. One area that is bugging me is that in the sample, I can add an image to a product and it gets saved to the database, but if I edit any given product, without uploading a new image for it, the image data is cleared out. I want to be able to edit a product, but if the image data returned from the HTTP post is null, that I want Entity Framework to keep the exisiting image data (and content type). How can I command EF to not update this image field with null if a new image isn't uploaded?
[HttpPost]
public ActionResult Edit(int id, HttpPostedFileBase image1, FormCollection collection)
{
using (ISession session = Database.OpenSession())
{
try
{
DoctorsModel db = new DoctorsModel();
db.Id_d = id;
db.D_city = collection["D_city"].ToString();
db.D_egn = collection["D_egn"].ToString();
db.D_email = collection["D_email"].ToString();
db.D_family_name = collection["D_family_name"].ToString();
db.D_first_name = collection["D_first_name"].ToString();
db.D_gender = collection["D_gender"].ToString();
db.D_mid_name = collection["D_mid_name"].ToString();
db.D_phone = collection["D_phone"].ToString();
db.D_specialty = collection["D_specialty"].ToString();
db.D_room = collection["D_room"].ToString();
db.D_floor = collection["D_floor"].ToString();
if (image1 != null)
{
db.D_picture = new byte[image1.ContentLength];
image1.InputStream.Read(db.D_picture, 0, image1.ContentLength);
}
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(db);
transaction.Commit();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
Model
public class DoctorsModel
{
public virtual int Id_d { get; set; }
[Display (Name ="Име: ")]
public virtual string D_first_name { get; set; }
[Display(Name = "Презиме: ")]
public virtual string D_mid_name { get; set; }
[Display(Name = "Фамилия: ")]
public virtual string D_family_name { get; set; }
[Display(Name = "Специалност: ")]
public virtual string D_specialty { get; set; }
[Display(Name = "Пол: ")]
public virtual string D_gender { get; set; }
[Display(Name = "Тел.номер: ")]
public virtual string D_phone { get; set; }
[Display(Name = "Email: ")]
public virtual string D_email { get; set; }
[Display(Name = "ЕГН: ")]
public virtual string D_egn { get; set; }
[Display(Name = "Град: ")]
public virtual string D_city { get; set; }
[Display(Name = "Снимка: ")]
public virtual byte[] D_picture { get; set; }
[StringLength(5)]
public virtual string D_rating { get; set; }
public virtual string D_content { get; set; }
[Display(Name = "Стая:" )]
public virtual string D_room { get; set; }
[Display(Name = "Етаж: ")]
public virtual string D_floor { get; set; }
}
The issue is going to be trying to use something like "SaveOrUpdate" if that is resolving to EF's AddOrUpdate which is intended for migrations & initial data population scenarios, or boiling down to a DbContext.Update(doctor) then your data gets overwritten with #null. You're giving EF an entity to insert or update without any consideration of data that is already there. EF will merely do an Exists check then issue an INSERT or effectively an UPDATE * based on ALL fields populated as they are in the provided entity.
Strictly for a RESTful API, you should be distinguishing between "Add" and "Update" by issuing a POST or PUT respectively. On POST you take the data, create a new model, add it to the DB Context and SaveChanges. For PUT you fetch the current model, update the appropriate values, and SaveChanges. A PUT operation should never insert the row if not found.
Your method looks like it's meant to only update an existing record:
[HttpPut]
public ActionResult Edit(int id, HttpPostedFileBase image1, FormCollection collection)
{
using (var context = new AppDbContext())
{
try
{
var doctor = context.Doctors.Single(x => x.Id == id); // Will throw if Dr not found.
doctor.D_city = collection["D_city"].ToString();
doctor.D_egn = collection["D_egn"].ToString();
doctor.D_email = collection["D_email"].ToString();
doctor.D_family_name = collection["D_family_name"].ToString();
doctor.D_first_name = collection["D_first_name"].ToString();
doctor.D_gender = collection["D_gender"].ToString();
doctor.D_mid_name = collection["D_mid_name"].ToString();
doctor.D_phone = collection["D_phone"].ToString();
doctor.D_specialty = collection["D_specialty"].ToString();
doctor.D_room = collection["D_room"].ToString();
doctor.D_floor = collection["D_floor"].ToString();
if (image1 != null)
{
doctor.D_picture = new byte[image1.ContentLength];
image1.InputStream.Read(doctor.D_picture, 0, image1.ContentLength);
}
context.SaveChanges();
return RedirectToAction("Index");
}
catch
{ // do more than this. :)
return View();
}
}
}
Ideally the DbContext or a UnitOfWork is injected. It's hard to know what you Session implementation does or interacts with the DbContext. I generally don't recommend trying to abstract away the EF DbContext as it really cripples the capability it offers for working with data entities efficiently. For update operations, fetch the existing entity and then copy the values across. The DbContext by default uses tracked entities so as you actually change values, those will be included into an UPDATE statement. Anything that isn't explicitly changed will be left as-is and doesn't get appended into the UPDATE query.
For potentially large data that is used infrequently like Images, I would recommend considering isolating those to a related table/entity. I.e.
public class DoctorImage
{
[Key]
public int Id { get; set; } // == Doctor.Id
public byte[] Image { get; set; }
}
Where the relationship of Doctor to DoctorImage is set up as HasOptional.WithRequired / HasOne.WithOne. The benefit is that loading the image when dealing with Doctors is entirely optional. Unless you use projection explicitly everywhere, loading an existing Doctor entity would cause EF to fetch the Image every time when you probably don't need it. By separating it, you only fetch it when explicitly eager/lazy loaded. Listing Doctors for instance can easily be done without the overhead of loading all of their image data.

Add child entities to database and assign them to parent object in database Entity Framework Core

I have a datamodel named "Document" which looks like this:
public class Document
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
//[ForeignKey("Id")]
public List<DocumentLine> Lines { get; set; }
public DateTime PurchaseDate { get; set; }
public double TotalValue { get; set; }
}
It contains a list of the child object "DocumentLines" which looks like this:
public class DocumentLine
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int LineId { get; set; }
public int DocumentId { get; set; }
//public virtual Document ParentDocument { get; set; }
public string ItemName { get; set; }
public double ItemPrice { get; set; }
public double ItemId { get; set; }
public int Quantity { get; set; }
}
I created a repository for each DataModel in my solution, so I have a documentRepository and a documentLinesRepository.
Currently I am facing several errors while creating a new document which contains a list of its child documentLines.
If i want to create a new document in the database the following code is executed first:
public async Task<Document> CreateNewAsync()
{
Document document = new Document()
{
Lines = null,
PurchaseDate = DateTime.Now,
};
var newDocument = await databaseContext.Documents.AddAsync(document);
await databaseContext.SaveChangesAsync();
return newDocument.Entity;
}
And this code works as it should, but now if I want to create a new object of type DocumentLine and add it to the database with the following code:
public async Task<DocumentLine> CreateNewAsync()
{
try
{
DocumentLine documentLine = new DocumentLine();
var newDocumentLine = await databaseContext.DocumentLines.AddAsync(documentLine);
await databaseContext.SaveChangesAsync();
return newDocumentLine.Entity;
}
catch(Exception ex)
{
return null;
}
}
I get the following error in the line where the "documentLine" should be added to the database:
"SQLite Error 19: 'FOREIGN KEY constraint failed'."
First of all I don't see where a foreign key is set for the instance of type "DocumentLine".
I tried several other things, as well as assigning a instance of the parent of type "Document" in each DocumentLine, but this got me an error as well.
How it should work
My question is how I could properly implement the behavior that a document is saved in the database and each line got saved seperatly (from the documentLinesRepository) and only at the end when all lines are set, the document will be updated in the database containing a full list of DocumentLines objects.
Sorry in advance if this is a beginner question, but I tried to solve this error for a few days until now and I didn't found the right solution with google atm.
"First of all I don't see where a foreign key is set for the instance of type "DocumentLine"."
Well, then first of all - check that. In the actual database created by EF. You're most probably gonna find an FK on DocumentId in your DocumentLines table.
Your Document and DocumentLines are in a 1toMany relationship - a DocumentLine makes no sense without a Document as a parent. So you can't insert an orphaned DocumentLine to the database. Uncomment the
public virtual Document ParentDocument { get; set; }
and assign the parent before inserting:
public async Task<DocumentLine> CreateNewAsync(Document parent)
{
try
{
DocumentLine documentLine = new DocumentLine();
documentLine.ParentDocument = parent;
var newDocumentLine = await databaseContext.DocumentLines.AddAsync(documentLine);
await databaseContext.SaveChangesAsync();
return newDocumentLine.Entity;
}
catch(Exception ex)
{
return null;
}
}
This tells EntityFramework "Hey, these two are related." EF then will figure out correct relationship values in the database by itself.
Tell me if it fixes the issue, as all is dependent on the initial guess of FK on DocumentId.

Entity framework cannot seem to create relationship

The entity framework isn't super new to me, however is confusing as I continue to expand my data models. I am attempting to create a class that has an array of another class. Class A or County.cs has a list of Class B or Product.cs
I cannot seem to create write these classes in a way that when you ask for context.counties you also get the array of products attached to it.
Class A or County.cs
public class County
{
[Key]
public int ID { get; set; }
public String Name { get; set; }
public List<Product> Products { get; set; } = new List<Product>();
[NotMapped]
public DateTime firstAppearance {
get {
var data = (from obj in Products orderby obj.Date descending select obj.Date).FirstOrDefault();
if (this.softwareIn)
{
return data;
}
else
{
var date = new DateTime(1,1,1);
return date;
}
}
set {
this.firstAppearance = value;
}
}
[NotMapped]
public bool softwareIn {
get {
return Products.Count() >= 1;
}
set {
this.softwareIn = value;
}
}
}
Class B or Product.cs
public class Product
{
[Key]
public int ID { get; set; }
public String Name { get; set; }
public DateTime Date { get; set; }
[NotMapped]
public DateTime DateUtc {
get {
return getUtcDate();
}
set {
this.DateUtc = value;
}
}
public DateTime getUtcDate() {
return this.Date.ToUniversalTime();
}
}
I just don't understand and haven't created enough of 1:M relations in the entity framework. Why cannot I do something like this and have it work all the time? The first time I run this I get the type of data I expect, the xx county has a product. However if I remove all this and just return the context.counties I get nothing in the products array.
[Route("Counties")]
public object GetCounties() {
var data = new County() {
Name = "xxx",
};
data.Products.Add(new Product() { Name="Cool Software", Date = DateTime.Now});
db.Counties.Add(data);
db.SaveChanges();
var da = db.Counties.ToList();
return db.Counties;
}
The reason you have having this issue is because the foreign keys are not correctly configured. Take a look at your database and look at the foreign keys. For Entity Framework to understand the relationships properly, you must mark related entities as virtual. So do this:
public virtual List<Product> Products { get; set;}
And then in your Product class add the navigation property back to the parent County:
public virtual County County { get; set;}
I found this tutorial really good:
http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
Hope this helps.

Entity Framework inserting new rows instead of updating them

I have a problem when I am updating data to database. When I want to update data, Entitiy Framework adds new rows to tables that can have multiple rows (tables that have foreign key).
Database model:
When I update Phone/Contact or Tags entity, Entity Framework automatically adds new row instead of updating it
Here is code that I used:
public string UpdateContact(Contact contact)
{
if (contact != null)
{
int id = Convert.ToInt32(contact.id);
Contact Updatecontact = db.Contacts.Where(a => a.id == id).FirstOrDefault();
Updatecontact.firstname = contact.firstname;
Updatecontact.lastname = contact.lastname;
Updatecontact.address = contact.address;
Updatecontact.bookmarked = contact.bookmarked;
Updatecontact.city = contact.city;
Updatecontact.notes = contact.notes;
Updatecontact.Emails1 = contact.Emails1;
Updatecontact.Phones1 = contact.Phones1;
Updatecontact.Tags1 = contact.Tags1;
db.SaveChanges();
return "Contact Updated";
}
else
{
return "Invalid Record";
}
}
EDIT:
Here is EF Model code:
Contact:
public partial class Contact
{
public Contact()
{
this.Emails1 = new HashSet<Email>();
this.Phones1 = new HashSet<Phone>();
this.Tags1 = new HashSet<Tag>();
}
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
public string city { get; set; }
public Nullable<byte> bookmarked { get; set; }
public string notes { get; set; }
public virtual ICollection<Email> Emails1 { get; set; }
public virtual ICollection<Phone> Phones1 { get; set; }
public virtual ICollection<Tag> Tags1 { get; set; }
}
Emails/Tags and Phone have same model (with different name for value)
public partial class Email
{
public int id { get; set; }
public int id_contact { get; set; }
public string email1 { get; set; }
public virtual Contact Contact1 { get; set; }
}
Update properties rather than set new objects.
Updatecontact.Emails1.email1 = contact.Emails1.email1;
Updatecontact.Phones1.number = contact.Phones1.number;
Updatecontact.Tags1.tag1 = contact.Tags1.tag1;
Edit: seems that your contact model has lists of emails, phones and tags. If this is so, then simple assignment won't work. Instead, when sent from the client, you have to find one-by-one and update:
foreach ( var email in contact.Emails1 )
{
// first make sure the object is retrieved from the database
var updateemail = Updatecontact.Emails1.FirstOrDefault( e => e.id == email.id );
// then update its properties
updateemail.email1 = email.email1;
}
// do the same for phones and tags
It's doing that because you're setting the different HashSet values to the values of a completely different collection, namely from what you call contact in that method. In order for you to properly do an update, you're going to have to loop through the emails, phones, and tags to check if those need to be added/updated/deleted on the actual object that you're trying to update.
First, why do you have to search for the contact if you are already receiving it by parameter? That makes me think that you are creating a new one because you are in a different context, if so, then it creates a new record because you have 2 different object in 2 different context.
Try using just one object in the same context to update, EF should mark the object to modification by itself, if not then try making sure before saving that your object has EntityState.Modified.

Why does my ASP.NET MVC 4 application create new entities instead of updating the old ones?

EDIT: The solution I selected probably wasn't the best, but it definitely worked. I'll be going through my code over the next week (once this project is done) and I'll update my question when I understand what went wrong.
I'm using the ASP.NET MVC 4 framework with Entity 5. Here's some code:
The class to be instantiated and saved (fresh) in the database:
public class ClassCancellation
{
[Key]
public int Id { get; set; }
public Faculty Professor { get; set; }
public DateTime CancelledOn { get; set; }
public Course Course { get; set; }
[Required]
public ClassDate ClassCancelled { get; set; }
public Message CancellationMessage { get; set; }
[Required]
public List<Student> Students { get; set; }
}
It's mapped from the viewmodel called CancellationFull (with AutoMapper):
public class CancellationForList
{
[Key]
public int Id { get; set; }
public CourseForList Course { get; set; }
public ClassDateForList ClassCancelled { get; set; }
}
public class CancellationFull : CancellationForList
{
public CancellationFull()
{
this.Students = new List<StudentForList>();
}
public FacultyForList Professor { get; set; }
public MessageForList CancellationMessage { get; set; }
public DateTime CancelledOn { get; set; }
public List<StudentForList> Students { get; set; }
}
This is the repo method that turns a CancellationFull into a ClassCancellation and then saves it to the database:
public CancellationFull createClassCancellation(CancellationFull c)
{
ClassCancellation newCancellation = Mapper.Map<ClassCancellation>(c);
dc.ClassCancellations.Add(newCancellation);
dc.SaveChanges();
return Mapper.Map<CancellationFull>(dc.ClassCancellations.FirstOrDefault(cc => cc.Id == newCancellation.Id));
}
Why, for the love of god why, does the database create new objects for Faculty and Course when the Id (primary key) of each's existing entity counterpart is provided? It might also be doing the same with Student objects but I haven't looked that closely.
Before the ClassCancellation instance is saved to the database the debugger shows that it's attributes Professor of type Faculty and Course of type Course have the correct primary key - that is, the primary key of the already existing entities of those types that I'm trying to update with a reference to the new ClassCancellation object.
Driving me nuts. Feel free to ask for clarification!
EDIT:
Here's the logic where the CancellationFull viewmodel is constructed from form data and viewmodels about existing objects retrieved from their respective repos:
newCancellation = new CancellationFull();
newCancellation.CancelledOn = DateTime.Now;
newCancellation.ClassCancelled = repoClass.getClassDateForListById(Int32.Parse(classIds[i]));
newCancellation.Course = repoCourse.getForList(newCancellation.ClassCancelled.Course.Id);
newCancellation.CancellationMessage = repoMessage.getMessageForList(newMessage.Id);
newCancellation.Professor = repoFac.getFacultyForList((int)Session["facId"]);
var students = repoStudent.getStudentsForListByCourse(newCancellation.Course.Id);
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
repoCancellation.createClassCancellation(newCancellation);
Here's an example of one of those repo methods (the rest are very similar):
public CourseForList getForList(int? id)
{
return Mapper.Map<CourseForList>(dc.Courses.FirstOrDefault(c => c.Id == id));
}
What I find the easiest solution is when updating a model, clear any related entities, then re add them.
ie:
newCancellation.Students.Clear();
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
Try using Attach() instead of Add()
dc.ClassCancellations.Attach(newCancellation);
dc.SaveChanges();
Add() is used for new objects that do not already exist in the database. Attach() is used for creating relationships to entities that already exist in the database.
EDIT
Without seeing your code, the best solution I can recommend to attach is to create a 'stub' instance and then attach that to your newCancellation:
var existingCourse = new Course{ Id = newCancellation.ClassCancelled.Course.Id };
db.Courses.Attach(existingCourse);
newCancellation.Course = existingCourse;
The problem is that you have multiple contexts, or units of work. When you add the newCancellation to the dc context, it also adds any related entity in the object graph that is not tracked in the dc context. I think your best option is:
dc.ClassCancellations.Add(newCancellation);
dc.Entry(newCancellation.Course).State = EntityState.Unchanged;
dc.Entry(newCancellation.Faculty).State = EntityState.Unchanged;
See Julie Lerman's article on this issue for an explanation and other options.
In my opinion, EF should recognize entities that have autonumbered keys and not insert them if the key is assigned.

Categories

Resources