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

Viewed 122

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; }
    
    
}
1 Answers

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.

Related