What is the best way of comparing that my image saved to a database isnt different, thus saving I/O.
Scenario:
Im writing an ASP.NET Application in MVC3 using Entity Framework. I have an Edit action method for my UserProfile Controller. Now i want to check that the image i have posted back to the method is different, and if it is, then i want to call the ObjectContext .SaveChanges() if it is the same image, then move on.
Here is a cut down version of my code:
[HttpPost, ActionName("Edit")]
public ActionResult Edit(UserProfile userprofile, HttpPostedFileBase imageLoad2)
{
Medium profileImage = new Medium();
if (ModelState.IsValid)
{
try
{
if (imageLoad2 != null)
{
if ((db.Media.Count(i => i.Unique_Key == userprofile.Unique_Key)) > 0)
{
profileImage = db.Media.SingleOrDefault(i => i.Unique_Key == userprofile.Unique_Key);
profileImage.Amend_Date = DateTime.Now;
profileImage.Source = Images.ImageToBinary(imageLoad2.InputStream);
profileImage.File_Size = imageLoad2.ContentLength;
profileImage.File_Name = imageLoad2.FileName;
profileImage.Content_Type = imageLoad2.ContentType;
profileImage.Height = Images.FromStreamHeight(imageLoad2.InputStream);
profileImage.Width = Images.FromStreamWidth(imageLoad2.InputStream);
db.ObjectStateManager.ChangeObjectState(profileImage, EntityState.Modified);
db.SaveChanges();
}
}
}
}
So i save my image as a varbinary(max) in nto a SQL Server Express DB, which is referenced as a byte array in my entities.
Is it just a case of looping around the byte array from the post and comparing it to the byte array pulled back into the ObjectContext?
Rather than directly comparing the byte array, I would compare the hash of the images. Perhaps something like the following could be extracted into a comparison method:
SHA256Managed sha = new SHA256Managed();
byte[] imgHash1 = sha.ComputeHash(imgBytes1);
byte[] imgHash2 = sha.ComputeHash(imgBytes2);
// compare the hashes
for (int i = 0; i < imgHash1.Length && i < imgHash2.Length; i++)
{
//found a non-match, exit the loop
if (!(imgHash1[i] == imgHash2[i]))
return false;
}
return true;
Related
I posted the question earlier, but didn't receive any correct responses, hence posting again with some edits. I have a function that accepts two parameters, IDs and Dates. When I had put breakpoints, I was able to see the Ids and the Dates selected on the page as parameter values. However, after hitting the process button, nothing happens, meaning this data isn't getting saved to the DB.
Model Classes:
public class Hello{
public string ID{ get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? Date{ get; set; }
}
Controller Class:
[HttpGet]
public ActionResult Selection(string ids, string dates)
{
model = new Hello();
ExtensionDB db = new ExtensionDB();
string[] IDS = ids.Split(',');
string[] DATES = dates.Split(',');
List<Hello> list = new List<Hello>();
for (int i = 0; i < IDS.Length; i++)
{
if (IDS[i] != null && IDS[i] != "")
{
Hello item = new Hello { ID = IDS[i], Date = DateTime.Parse(DATES[i]) };
list.Add(item);
}
}
if (ModelState.IsValid)
{
foreach (var row in db.Table1)
{
foreach (var row2 in db.Table2)
{
if (row.UID== row2.CID) // UID and CID are Foreign keys that join these two tables
{
foreach (var item in list)
{
if (row.UID == Convert.ToInt32(item.ID))
{
row2.ReportedDate = item.Date;
}
db.SaveChanges();
}
}
}
}
ViewBag.Message = "Success";
return View(model);
}
else
{
ViewBag.Message = "Failed";
return View(model);
}
}
I will add the view class if needed, however the problem is here.. You can also refer to it here: Saving changes to the DB MVC
Your code does not attempt to update anything. Start with confirming what the data you are passing to this POST call contains, and what you want to do with it. It looks like what you are trying to do is update the dates for a number of records. Looking at your previous post (no need to re-post another question with the same code) there are a few things..
First: Structure the data you want to pass to the POST call into a collection of simple objects containing an id and a date. For example:
{
id = rid,
date = date
}
and add those to the collection named something like "updateData" rather than two separate arrays of IDs and dates. Then in the server-side code, declare a simple view model class:
public class UpdateDateViewModel
{
public int Id { get; set; }
public DateTime Date { get; set; }
}
In the ajax call instead of:
data: { ids: ids, dates: dates },
you'll want something like:
data: { updates: updateData },
where updateData is your collection of id + date pairs.
and use that view model in your method:
public ActionResult Process(IList updates)
Provided that request data is sent as Json, ASP.Net should translate that data automatically for you, though you may need to configure ASP.Net to translate the camelCase vs PascalCase. Worst case, to test, you can use camelCase property names ("id" and "date")
Now when it comes to updating the data: Server side, please get in the habit of using meaningful variable names, not "c", "i", etc. It makes code a lot easier to understand.
public ActionResult Process(IList<UpdateDateViewModel> updates)
{
using (db = new DB())
{
//rp = new RequestProcess(); - Assuming RequestProcess is an Entity?
//var c = rp.getStuff(); - No idea what this getStuff() method does...
foreach(var update in updates)
{
var request = db.RequestProcesses.Find(update.Id);
if (request != null)
request.RequestDate = update.Date; // If we find a matching request, update it's date.
else
{ // Doesn't exist, create it and add it to the DbSet.(table)
var request = new RequestProcess { Id = update.Id, RequestDate = update.Date };
db.RequestProcesses.Add(request);
}
db.SaveChanges();
}
}
}
Now this is a very bare bones guess at what you may be trying to do. Ideally though, updates should be completely separate from adds in the sense that an update should only deal with existing records. If it comes across an ID that it cannot find it should throw an error, ignore, and/or return a status to the user that something wasn't right. Creating new entries should be a separate call and ensure that records are properly initialized with their required fields.
Your original code looked to be taking a list of IDs, but then creating a new entity and calling that "getStuff" method that didn't have the DbContext, or any of the values from the POST call, but then attempting to copy values from that entity into the string parameters that you passed (which would overwrite the Json string) None of that would have updated an entity which would never have updated your data.
Take it slow and follow the examples before attempting to adapt them to your ideas. It will be a lot more constructive and less frustrating then writing a bunch of code that doesn't really make much sense, then wondering why it doesn't work. Your original code has probably a dozen or more problems and inefficiencies. Simply pasting it up on Stack will get a lot of confusing comments based on these problems which don't really help with the first issue you want to solve. Strip it back to the minimum, start with getting the data you need to the server in a meaningful way, then from that, attempt to use that data to update your entities.
Hi I am working on an EF Using Model First.
I want to add more details in table that specify for special categorys
but when I add these details that depend on adding data in a product table I get an exception.
public static void addproduct(ProductViewModel vm ,string userid)
{
var model = Mapper.Map<ProductViewModel, Product>(vm);
model.CountSell = 0;
byte[] binaries = new byte[vm.postprofilimgae.ContentLength];
vm.postprofilimgae.InputStream.Read(binaries, 0, vm.postprofilimgae.ContentLength);
model.ProfileImage = binaries;
foreach (var file in vm.postProductimages)
{
var filedetails = new ProductImage();
var data = new byte[file.ContentLength];
filedetails.ContentLength = file.ContentLength;
filedetails.FileName = file.FileName;
file.InputStream.Read(data, 0, file.ContentLength);
filedetails.ProductImage1 = data;
filedetails.ProductID= model.ProductID;
model.ProductImages.Add(filedetails);
}
using (var db = new Entities())
{
model.ActivationID = db.ActivationStatus.Single(x => x.Name == "Active").ActivationID;
model.SubCategoryID = vm.SubCategoryID;
model.EmployeeID= db.Employees.Single(x => x.UserID == userid).EmployeeID;
model.CountSell = 0;
VichDetails detailsmodel = new VichDetails();
if (db.SubCategories.Single(x=>x.SubCategoryID == vm.SubCategoryID).MainCategoryID == db.MainCategories.Single(x=> x.Name == Categories.vehicles).MainCategoryID)
{
VichDetails moredetails = new VichDetails();
detailsmodel = Mapper.Map<ProductViewModel, VichDetails>(vm);
//detailsmodel.ProductID = model.ProductID;
db.VichDetails.Add(detailsmodel);
}
db.SaveChanges();
model.VichDetailsID = detailsmodel.Id;
db.Products.Add(model);
db.SaveChanges();
}
}
I get this exception:
In your Product you will want to consider adding:
public virtual VichDetail VichDetail { get; set; }
and then instead of:
if (db.SubCategories.Single(x=>x.SubCategoryID == vm.SubCategoryID).MainCategoryID == db.MainCategories.Single(x=> x.Name == Categories.vehicles).MainCategoryID)
{
VichDetails moredetails = new VichDetails();
detailsmodel = Mapper.Map<ProductViewModel, VichDetails>(vm);
db.VichDetails.Add(detailsmodel);
}
db.SaveChanges();
model.VichDetailsID = detailsmodel.Id;
db.Products.Add(model);
db.SaveChanges();
it would look like:
if (db.SubCategories.Single(x=>x.SubCategoryID == vm.SubCategoryID).MainCategoryID == db.MainCategories.Single(x=> x.Name == Categories.vehicles).MainCategoryID)
{
VichDetails detailsmodel = Mapper.Map<ProductViewModel, VichDetails>(vm);
model.VichDetails = detailsModel;
}
db.Products.Add(model);
db.SaveChanges();
The issue you are encountering is that while you are setting the new VichDetail ID on your Product before saving the product, you have only associated the Product, not necessarily the VichDetail. I.e.
if (db.SubCategories.Single(x=>x.SubCategoryID == vm.SubCategoryID).MainCategoryID == db.MainCategories.Single(x=> x.Name == Categories.vehicles).MainCategoryID)
If this condition is false, the VichDetail is not added to the DbContext.
The code is rather confusing to look at as you are creating a VichDetail, but then checking that condition before creating another VichDetail "moredetails" which doesn't look to be used, before mapping the original details from the view model. If that condition doesn't pass you will have an empty VichDetails (detailsmodel) with likely a default ID (0?) which would be set on your Product. (model)
You cannot simply move the model.VichDetailsID = detailsmodel.id inside your conditions, since you're relying on setting FK IDs which won't be available until the related entity is saved to the DbContext. Navigation properties are generally a lot easier to interact with than messing around with PKs and FKs in your entities. Let EF do the lifting for associating the relationships.
If your entities already have these navigation properties, then you'll want to use those and avoid setting FKs directly.
Currently I am doing like this:
For Example:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
Just wondering is there any elegant way to do this with less effort and optimal performance?
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
In this example, you have a single entity called Person:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
First, create a view model:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
Anyway, the result when SaveChanges is called is the following SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = #0
WHERE ([Id] = #1)
',N'#0 datetime2(7),#1 int',#0='2015-01-01 00:00:00',#1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
I hope this helps you.
I swear by EntityFramework.Extended. Nuget Link
It lets you write:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
Please try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
I have provided my code sample below; Great if it can help someone else
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
I still didn't find a nice solution for my problem, so I created a work around. When loading the Entity, I directly make a copy of it and name it entityInit. When saving the Entity, I compare the both to see, what really was changed. All the unchanged Properties, I set to unchanged and fill them with the Database-Values. This was necessary for my Entities without Tracking:
// load entity without tracking
var entityWithoutTracking = Context.Person.AsNoTracking().FirstOrDefault(x => x.ID == _entity.ID);
var entityInit = CopyEntity(entityWithoutTracking);
// do business logic and change entity
entityWithoutTracking.surname = newValue;
// for saving, find entity in context
var entity = Context.Person.FirstOrDefault(x => x.ID == _entity.ID);
var entry = Context.Entry(entity);
entry.CurrentValues.SetValues(entityWithoutTracking);
entry.State = EntityState.Modified;
// get List of all changed properties (in my case these are all existing properties, including those which shouldn't have changed)
var changedPropertiesList = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
foreach (var checkProperty in changedPropertiesList)
{
try
{
var p1 = entityWithoutTracking.GetType().GetProperty(checkProperty).GetValue(entityWithoutTracking);
var p2 = entityInit.GetType().GetProperty(checkProperty).GetValue(entityInit);
if ((p1 == null && p2 == null) || p1.Equals(p2))
{
entry.Property(checkProperty).CurrentValue = entry.Property(checkProperty).OriginalValue; // restore DB-Value
entry.Property(checkProperty).IsModified = false; // throws Exception for Primary Keys
}
} catch(Exception) { }
}
Context.SaveChanges(); // only surname will be updated
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
In EF you can do like this
var result = db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
if(result != null){
result.Name = newName;
result.DOB = newDOB;
db.Person.Update(result);
}
Or you can use
using (var db= new MyDbContext())
{
var result= db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
result.Name= newName;
result.DOB = newDOB;
db.Update(result);
db.SaveChanges();
}
For more detail please EntityFramework Core - Update Only One Field
No Worry guys
Just write raw sql query
db.Database.ExecuteSqlCommand("Update Person set Name='"+_entity.Name+"' where Id = " + _entity.ID + "");
Is the essence of Project, the creation of which is necessary to check whether there is already an entity with the same name. When editing needs such as checking, but keep in mind that the old and the new name of the entity can be matched.
You also need to display an error message. For this I use interface IValidatableObject, but do not know how to tell the Validate method the object is currently being edited or created
DbContext.ValidateEntity takes the IDictionary<Object, Object> items as the second parameter. You can pass any data there and the data you pass will be passed to IValidatableObject.Validate in the ValidationContext.Items
Assuming you refer to check EF cant do for you.
This is actually difficult to check. You are checking an entity after it has been added to the context. It should not check itself and needs to consider other items in context that are not yet saved. As well as the DB. There are several 3 combinations plus an self recognition. Record a an entity record in LOCAL when ID is blank/new ie multiple new inserts needs careful coding. (Consider using temp IDs)
the not yet saved entries should be in context
Context.Set<TPoco>().Local
and get data from DB and keep in a temp list. BUT dont put in context.
Or use a SECOND context.
var matchingSet = Context.Set<TPoco>().AsNoTracking() // not into context...
.Where(t=>t.field == somevalue).ToList();
So what about logical and actual duplicates on the DB. Logical duplicates are duplicates on a field with no unique index that from a business perspective should be unique.
If you want to check those...
You need to read the DB.... BUT if these records are currently being changed, you CAN NOT just put them into the Context. You would overwrite them.
But what if the values the logical key values have changed?
Something caused a logical dup on a record on the DB may no longer be a dup once saved or vice verse. Is that still a dup or not ?
So you need to decide how you match LOCAL versus loaded records.
Ie check LOCAL and matching DB records and decidr what to do if a record is in both, only local or only db.
LOCAL ONLY and DB Only is easy.
But in both... That is your business process decision.
Problem is solved using method ModelState.AddModelError (string, string) in actions Edit and Create.
[HttpPost]
[HandleError(View="AjaxError")]
public ActionResult Edit(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
int projectId = (from p in db.Projects
where p.Name == data.CurrentObject.Name
select p.ProjectID).FirstOrDefault();
if (projectId == data.CurrentObject.ProjectID)
{
db.Projects.Attach(data.CurrentObject);
db.ObjectStateManager.ChangeObjectState(data.CurrentObject, EntityState.Modified);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
}
data.ObjectToEdit = data.CurrentObject;
return Projects(data);
}
[HttpPost]
[HandleError(View = "AjaxError")]
public ActionResult Create(ProjectsViewData data)
{
if (ModelState.IsValid)
{
if (!ContainsProject(data.CurrentObject.Name))
{
db.Projects.AddObject(data.CurrentObject);
db.SaveChanges();
return Projects(data);
}
else
{
ModelState.AddModelError("Name", Localizer.ProjectAlreadyExists);
}
}
data.ObjectToAdd = data.CurrentObject;
return Projects(data);
}
Helper method:
private bool ContainsProject(string projectName)
{
if (projectName != null)
{
projectName = Regex.Replace(projectName.Trim(), "\\s+", " ");
List<string> projects = new List<string>();
var projectNames = (from p in db.Projects
select p.Name.Trim()).ToList();
foreach (string p in projectNames)
{
projects.Add(Regex.Replace(p, "\\s+", " "));
}
if (projects.Contains(projectName))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
I need to display in image on the view this way
<img src = <% = Url.Action("GetImage", "Home", new { productID })%>
This is the action which's supposed to supply data
public FileContentResult GetImage(int ID)
{
var img = db.Images.Where(p => p.ID == ID).First();
return File(img.ImageData, img.ImageMimeType);
}
This example comes from Pro ASPNET.NET MVC (Steven Sanderson/APress). I'm getting the following error: The best overload method match for System.Web.Mvc.Controller.File(string, string) has some invalid argument. Cannot convert from System.Data.Linq.Binary to string.
Yet, the intellisense's telling me that there's an overload method (byte[] filecontents, string fileType).But, when I write the above code, I get the error. Am I missing something?
EDIT
Thanks for the answer. I've experienced a similar problem while uploading the image file. Here's my action method
public ActionResult AddImage(HttpPostedFileBase image)
{
if(image != null)
{
var img = new Image();//This Image class has been
//created by the DataContext
img.ImageMimeType = image.ImageMimeType
img.ImageData = new byte[image.ContentLength];
image.InputStream.Read(img.ImageData, 0, image.ContentLength);
}
}
I get error for the last line "image.InputStream.Read(myImage.ImageData, 0, image.ContentLength);" It's saying that it can't convert System.Data.Linq.Binary to Byte[]
What I did was (i) to create a new class, called ImageDataClass, (ii) do the above operation against that class, (iii) do the explicit conversion from ImageDataClass to Image, and (iv) save to the DB using Linq.
I don't think it should be that complicate. Is there any way to make it work using simply an extension method such as ToArray as for the other case???
Thanks for helping
There is an overload for File() that takes a byte array, but you are trying to pass in a type of System.Data.Linq.Binary, not a byte array. However, there is a method on Binary to convert to a byte array.
Try this:
public FileContentResult GetImage(int ID)
{
var img = db.Images.Where(p => p.ID == ID).First();
return File(img.ImageData.ToArray(), img.ImageMimeType);
}
The reason the compile error mentions "string" is purely because it can't work out which overload you were trying for, so it just picks one, in this case string, and then reports the type conversion error.
[EDIT: in response to OP edit]
You should be able to try something like this:
public ActionResult AddImage(HttpPostedFileBase image)
{
if(image != null)
{
var img = new Image();//This Image class has been
//created by the DataContext
img.ImageMimeType = image.ImageMimeType
var imageData = new byte[image.ContentLength];
image.InputStream.Read(imageData, 0, image.ContentLength);
img.ImageData = new System.Data.Linq.Binary(imageData);
}
}
Remember that although System.Data.Linq.Binary is probably just a byte array underneath, or is at least intended to represent byte data, it is not itself of type byte[]; you still have to convert to and from (a similar situation to System.IO.MemoryStream)