How to partly update a database record? - c#

I am struggling to implement a simple 'update profile' functionality(learning purposes). I simply want to be able not to update the profile image everytime I update a give profile. When the picture is there and some other part of the profile is update I want the picture to stay the same.
I came up with the following code for this :
Controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "UserDetailsId,ImageData,FirstName,LastName,UserAddress,UserCountry,UserPostalCode,UserPhoneNumber,CompanyId,identtyUserId")] UserDetails userDetails, HttpPostedFileBase UploadImage)
{
if (ModelState.IsValid)
{
if (UploadImage!=null) {
byte[] buf = new byte[UploadImage.ContentLength];
UploadImage.InputStream.Read(buf, 0, buf.Length);
userDetails.ImageData = buf;
}
else {
var userFromDb = db.UsersDetails.Where(u => u.identtyUserId == userDetails.identtyUserId).First();//i am getting the old user data
userDetails.ImageData = userFromDb.ImageData; //saving the image to the modified state
}
db.Entry(userDetails).State = EntityState.Modified;//error here
db.SaveChanges();
return RedirectToAction("Index");
}
//ViewBag.CompanyId = new SelectList(db.Companies, "CompanyId", "CompanyName", userDetails.CompanyId);
return View(userDetails);
The error I am getting on this row db.Entry(userDetails).State = EntityState.Modified; is the following :
Attaching an entity of type 'eksp.Models.UserDetails' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
The model :
public class UserDetails
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserDetailsId { get; set; }
public byte[] ImageData { get; set; }
[NotMapped]
public HttpPostedFileBase UploadImage { get; set; }
[NotMapped]
public string ImageBase64 => System.Convert.ToBase64String(ImageData);
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserPhoneNumber { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public string identtyUserId { get; set; }
public virtual ICollection<WorkRolesUsersDetails> WorkRolesUsersDetails { get; set; }
Although it may looke pretty self explenatory for me it is not clear this is happening?
Can somebody guide me how to achieve what I want to achieve?
Thanks!

When you update an entity, you should map the posted values onto an instance pulled from the database, rather than trying to directly save the instance created from the post data. This is yet another reason to avoid using Bind as it confuses the issue and makes developers who don't know better think it's okay to directly save the entity created from the post data. It's not, and never is.
Instead, use something like UserDetailsId to lookup the entity:
var userDetails = db.UserDetails.Find(model.UserDetailsId);
Where model is the parameter from your action. Then, you can map over the posted values onto your entity:
userDetails.FirstName = model.FirstName;
// etc.
Finally, save usersDetails, which is now the version from the database, with all the original data on the entity, modified to the posted data, where appropriate.
Now, given that you need to do this mapping over of the posted data anyways, go a step further can create a view model with just the properties you need to allow the user to modify. You can then post to that, instead of using Bind. Really, Bind is just awful. It's one of those things Microsoft hastily adds because they think it solves one problem, and it actually ends up causing ten other problems.

You can retrieve data entity from the db in any case and update it with the details coming as part of the Post Model and Save that data entity back to the db.
var userFromDb = db.UsersDetails.Where(u => u.identtyUserId == userDetails.identtyUserId).First();
if (UploadImage!=null)
{
byte[] buf = new byte[UploadImage.ContentLength];
UploadImage.InputStream.Read(buf, 0, buf.Length);
userFromDb.ImageData = buf;
}
userFromDb.FirstName = userDetails.FirstName;
userFromDb.LastName = userDetails.LastName;
userFromDb.UserAddress = userDetails.UserAddress;
userFromDb.UserCountry = userDetails.UserCountry;
userFromDb.UserPostalCode = userDetails.UserPostalCode;
userFromDb.UserPhoneNumber = userDetails.PhoneNumber;
userFromDb.CompanyId = userDetails.CompanyId;
db.SaveChanges();
This should help you to achieve the feature you want.

Related

Updating Entity Framework many-to-one Relationship

I'm trying to update a one-to-many relationship with EntityFramework, but EF won't save the relationship for some reason. I'm using ASP.Net MVC, but that does not seem to matter in this case as the data is received correctly.
I've tried a lot of possible solutions and some tutorials, unfortunately almost all of them describe a scenario where the connection is made via a foreign key property in the class itself.(I'm aware that EF adds a FK in the database, but i cant access that directly.) My approach seems to be significantly different as none of their solution seems to work for me.
The code below seems to me to be the most promising, but it still doesn't work. the foreign key of the activity object doesn't get updated.
Removing context.Entry(act.ActivityGroup).State = EntityState.Detached; causes a Primary Key collision, as EF tries to insert the ActivityGroup as a new Entity. Marking it as Modified, doesn't do the trick either.
Models:
public class Activity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ActivityID { get; set; }
public string Name { get; set; }
public ActivityGroup ActivityGroup { get; set; }
}
public class ActivityGroup
{
public int ActivityGroupID { get; set; }
public string GroupName { get; set; }
public string BackgroundColor { get; set; }
}
Method to save Data
public ActionResult SaveActivities(List<Activity> activities)
{
if (ModelState.IsValid)
{
using (TSSDBContext context = new TSSDBContext())
{
foreach (Activity act in activities)
{
if (act.ActivityGroup != null)
{
context.Entry(act.ActivityGroup).State = EntityState.Detached;
}
context.Entry(act).State = (act.ActivityID == null || act.ActivityID == Guid.Empty) ? EntityState.Added : EntityState.Modified;
}
context.SaveChanges();
return new HttpStatusCodeResult(200);
}
}else
{
return new HttpStatusCodeResult(500);
}
}
You could try something like this.
EF context is tracking each entity you don't need manually marking
entities , Modified or Added for each. Read about Entityframework context tracking
Just fetch the entities what you need and decide to insert or update on based on your condition and just Add what should be added and update
Just do a SaveChanges EF will show the magic
This is a basic idea of inserting and updating entities at one shot. If you have concerns about performance i would suggest you to update using AddRange method in EF 6.0
using(var db1 = new Entities1())
{
var activitylists = db.Activity.ToList();
foreach (var item in activitylists )
{
if(item.Id==null)
{
var newActivity= new Activity();
//Your entities
newActivity.Name="Name";
db.Activity.Add(newActivity);
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Added;
}
else
{
item.Name="new name update";
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Modified;
}
}
db.SaveChanges();
}
Update : if your getting data from PostRequest , you need to manually mark the entities as modified or added as the context is not aware of what to do with entities

Entity Framework in disconnected scenario throws saving or accepting changes failed because more than one entity of type

I am working in a small project using Web API, and entity Framework. And I facing some issue in posting my entity.
My entities look like this:
public class DayExercises
{
public DayExercises()
{
Exercises = new List<Exercise>();
}
[Key]
public int Id { get; set; }
public string Day { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
}
and my Exercise entity look like this.
public class Exercise
{
public Exercise()
{
DayExercises = new List<DayExercises>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<DayExercises> DayExercises { get; set; }
}
and my web api method for posting the dayExercises look like this
[ResponseType(typeof(WorkoutTemplate))]
public IHttpActionResult PostWorkoutTemplate(DayExercises dayExercises)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (var dayExercise in dayExercises)
{
fitnessDbContext.Entry(dayExercise).State = EntityState.Added;
foreach (var exercise in dayExercise.Exercises.ToList())
{
fitnessDbContext.Entry(exercise).State = EntityState.Unchanged;
}
}
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = dayExercises.Id }, dayExercises);
}
The relationship is Many-To-Many.
Problem:
I am sending a dayExercise with existing Exercise(already existing in database) to my method. but when I'm posting dayExercise with SAME two exercises. it throw that exception:
Additional information: Saving or accepting changes failed because more than one entity of type 'FitnessFirst.WebApi.Exercise' have the same primary key value.
Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model.
Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
I also tried to Deattach the entities, and Get the exercise from Database using their ID and Attach them again and add it to the dayExercise but it doesn't save to database.
NOTE: when I add two different exercises, it doesn't throw that Exception.
I also read the following answers, but it doesn't solve it: Ensure that explicitly set primary key values are unique
Any Suggestion or explanation.
I know this is an old post, but I ran into the same issue yesterday. This is the solution I came up with. Basically entity framework change tracker only allows unique values for an entity. So to get around the error you need to check if the entity already exists in the change tracker and use that instance.
var excercises = dayExercise.Exercises.ToList();
for (int i = 0; i < excercises.Count; i++)
{
var unchangedEntity = _dbContext.ChangeTracker.Entries<Exercise>()
.Where(xy => xy.State == EntityState.Unchanged &&
xy.Entity.Id == excercises [i].Id).FirstOrDefault();
if (unchangedEntity == null)
{
fitnessDbContext.Entry(excercises[i]).State = EntityState.Unchanged;
}
else
{
excercises[i] = unchangedEntity.Entity;
}
}

With Web API, How does Entity Framework know what has changed in an object?

I am using EF5 with SQL Server 2012 in a web application. I have two classes:
public partial class Topic {
public Topic()
{
this.SubTopics = new List<SubTopic>();
}
public int TopicId { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public int TopicId { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public virtual byte[] Version { get; set; }
public virtual Topic Topic { get; set; }
}
In our front-end we make a change to SubTopic Notes and then when a Save button is pressed the Topic object together with its SubTopic Objects are sent back as JSON to the Web API Controller. When we check what is being sent back we see the new data for the Notes. When we check the parameter topic we also see the new Notes data.
public HttpResponseMessage PutTopic(int id, Topic topic)
{
_uow.Topics.Update(topic);
_uow.Commit();
}
However checking with SQL Profiler we cannot see anything happening to change the Sub Topic. When data is retrieved the old SubTopic data is returned and the edit to notes is lost.
For the case of a Web update like this. How does EF determine what has changed and is there some way we can make it check / compare what's there with the new object so that it can detect a change and also update the Subtopic ?
Configuration:
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = true;
DbContext.Configuration.AutoDetectChangesEnabled = false;
Update Method:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
Since ef4.x? EF has a detect changes capability. And it can be made to work for you in an offline object mode.
I assume your UoW handling isn't doing what you would like because of how objects are placed in the context and how the state is managed.
Your main object is attaching an object and then setting it as state changed. But what about all the sub objects? Did they get loaded into the context? Does autodetect changes have something to check?
If it is not in the context, then EF cant see the change.
it is important to know when and how EF tracks changes
tracking changes in Poco Objects
Check the auto detect settings.
this.Configuration.AutoDetectChangesEnabled = false; ////<<<<<<<<< Default true
A pattern that works with EF is:
var mypoco = Context.Set<TPoco>.Find(1); // find and LOAD your poco or sub object poco
// repeat for graph or use include etc...
// now in context and changes can be tracked. (see link)
// map json fields to poco entity....
myPoco.propertyXyz = json.ValuesSent; // some mapping approach to move your values, I use this package
// <package id="ValueInjecter" version="2.3.3" targetFramework="net45" />
// now tell EF things might have changed.
// normally not required by default, But incase your are not using tracking proxies , tell ef check for changes
// Context.Context.ChangeTracker.DetectChanges(); // uncomment when needed
Context.SaveChanged(); // will trigger detect changes in normal scenarios
EF checks those entities loaded for any changes, it has an original state when first attached.
So it can now see which properties have changed.
It will only update those changed entities and it will Only set the changed properties.
It seems that the problem is similar to a questions I answered yesterday. Please take a look at my answer

MVC/EF - How to add an entity association

I have an Entity in EF called Advertiser and another called Client. Advertisers have a association field called Client, which is selected from a dropdownlist. I want to know how to save this association to the database. The approach I've used is to find the Client object (by using the Id) and then assign this Client object to the Advertiser.Client navigation property. I hoped that by then Adding this Advertiser property, I'd have added an Advertiser that is associated with an existing Entity. However this was not the result. Instead, a new Client record also got added to the table. How do I fix this?
Full explanation and code bits are below...
public class Advertiser
{
public int Id { get; set; }
public string Name { get; set; }
//Navigation Properties
public virtual Client Client { get; set; }
}
And another called Client
public class Client
{
public Client()
{
Advertisers = new List<Advertiser>();
}
public int Id { get; set; }
public string Name { get; set; }
// Navigation Properties
public virtual ICollection<Advertiser> Advertisers { get; set; }
}
A bunch of clients are added to the database in a separate view. When the user lands on the Advertiser views, they have the create option. What I want the create to do is allow the user to pick a client from a drop down list containing all clients. I want this advertiser to then be associated with that client.
This is the code for the controller:
//
// POST: /Advertiser/Create
[HttpPost]
public ActionResult Create(Advertiser advertiser,int Id)
{
if (ModelState.IsValid)
{
advertiser.Client = clientRepo.Retrieve(Id); // Finds and returns a Client object
System.Diagnostics.Debug.WriteLine("Saving Advertiser, with int Id = {0} and Client.Id = {1}", Id, advertiser.Client.Id);
repo.Create(advertiser);
repo.SaveChanges();
return RedirectToAction("Index");
}
return View(advertiser);
}
The Advertiser view populates a dropdownlist with all the Clients and then returns the Id for the currently selected Client.
<div class="editor-field">
#{
var clients = new Repository<Client>().FetchAll();
var clientLists = new SelectList(clients, "Id", "Name");
}
#Html.DropDownList("Id", clientLists)
</div>
Now, this view correctly returns the correct Id. The Debug.Writeline also confirms that the correct Id is being passed back. The problem lies in what happens after that...
Instead of inserting a new Advertiser that is associated with the existing Client entity, what it does is, it first inserts an Advertiser, and then inserts a copy of the Client entity to the database. This results in duplicate Clients that differ only in primary key (Id),
I know this can be solved by exposing the foreign key and passing the foreign key instead of finding and referencing the appropriate Client to the the Advertiser.Client property. But if possible I'd prefer to do this without exposing foreign keys. Is there some way this can be done? ... i.e. What am I doing wrong?
If what goes on in the Repository class could be useful to answer this question, I've added it below:
public OperationStatus Create(TEntity item)
{
OperationStatus status = new OperationStatus {Status = true};
var value = DataContext.Set<TEntity>().Add(item);
if(value == null)
{
status = null;
}
return status;
}
public TEntity Retrieve(int id)
{
return DataContext.Set<TEntity>().Find(id);
}
Add a [Key] Attribute to the ID property on both Client and Advertiser

Cannot update many-to-many relationships in Entity Framework

I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example

Categories