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
Related
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.
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;
}
}
There are 2 classes
Event
public class Event
{
public Guid? UserID { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; }
...
User
public class User
{
public Guid UserId { get; set; }
// Not used in this example, but just thought they might be related to problem
private List<Event> _attendedEvents;
public virtual ICollection<Event> AttendedEvents
{
get { return _attendedEvents ?? (_attendedEvents = new List<Event>()); }
set {
if (value == null)
_attendedEvents = new List<Event>();
else
_attendedEvents = new List<Event>(value);
}
}
public virtual ICollection<Event> HostedEvents { get; set; }
...
EventConfiguration
HasOptional<User>(s => s.User)
.WithMany(s => s.HostedEvents)
.HasForeignKey(s => s.UserID);
The thing I'm trying to do is
Add User to Repository
Add Event(which has same User inside it) to Repository
Save Changes
Retrieve Event back from repository
Everything kind of works, except when I retrieve Event back it has null User, however UserId is valid and points to User i created earlier.
Here's how I'm doing it
// Creates just the User object with specified UserName
var user = ObjectHelpers.CreateUser("ServiceTestUser");
// Adds to Repository + Saves Changes
_client.AddUser(user);
// Find it again to have generated Id and kind of test if it was added
user = _client.FindUserByEmail(user.Email);
// Create Event object and assign specified user object to it
// At this point #event has User set to above one and UserID null
var #event = ObjectHelpers.CreateEvent(user);
// Attach User from Event + Add Event to repository + Save Changes
_client.AddEvent(#event);
// Get it back to Check if everything went fine
// At this point #event has User set to null and valid UserID
#event = _client.GetEventByTitle(#event.EventTitle);
By default EF will not read related entities. And this behavior is really useful. If not, whenever you tried to read an entity from the DB, you'd read that entity, and all the probably very big, tree of related entities.
You must read realted entities:
explicitly, by using .Include()
or implicitly, by using lazy loading, which means accessing the navigation property provided the DbContext hasnot been disposed
Example of Include():
DbCtx.Events.First(ev => ev.Title == "title").Include(ev => ev.User);
For more information on including related entities see this: Loading Related Entities
So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.
I have a situation where I have an object that is loaded back from a form to MVC controller via an action. We do not use FormCollection, but the one that use directly the class.
[HttpPost]
public ActionResult AjaxUpdate(Customer customer) { ...
The Customer object contain an object called customer which seem to be updated but when using SaveDatabase() on the context simply doesn't work.
To make it works I had to use in the action:
myDbContext.Customers.Attach(customer)
//...Code here that set to the customer.SubObject a real object from the database so I am sure that the SubObject contain an id which is valid and the datacontext is aware of it...
myDbContext.Entry(customer).State = EntityState.Modified;
Still, I had an exception concerning the "Store update, insert, or delete statement affected an unexpected number of rows (0)" that I were able to remove by using:
Database.ObjectContext().Refresh(RefreshMode.ClientWins,customer);
So, to warp up my question, why do I have to Attach + change the state + call Refresh. Isn't there a better way to update an object that contain object that are referenced in an other table. I am using Code first Entity Framework (Poco object). Also, I do not like to use Refresh since it's hidden from my Databasecontext.
I've made a console test project with EF 4.3.1. The code is my guess what you mean with the commented line and your comments below the question (but my guess is probably wrong because the program doesn't reproduce your error):
You can copy the code into program.cs and add a reference to EF 4.3.1:
using System.Data;
using System.Data.Entity;
namespace EFUpdateTest
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int SubObjectId { get; set; }
public SubObject SubObject { get; set; }
}
public class SubObject
{
public int Id { get; set; }
public string Something { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<SubObject> SubObjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
int customerId = 0;
int subObject1Id = 0;
int subObject2Id = 0;
using (var ctx = new CustomerContext())
{
// Create customer with subobject
var customer = new Customer { Name = "John" };
var subObject = new SubObject { Something = "SubObject 1" };
customer.SubObject = subObject;
ctx.Customers.Add(customer);
// Create a second subobject, not related to any customer
var subObject2 = new SubObject { Something = "SubObject 2" };
ctx.SubObjects.Add(subObject2);
ctx.SaveChanges();
customerId = customer.Id;
subObject1Id = subObject.Id;
subObject2Id = subObject2.Id;
}
// New context, simulate detached scenario -> MVC action
using (var ctx = new CustomerContext())
{
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
}
}
}
}
This works without exception. The question is: What is different in your code which could cause the error?
Edit
To your comment that you don't have a foreign key property SubObjectId in the customer class: If I remove the property in the example program above I can reproduce the error.
The solution is to load the original subobject from the database before you change the relationship:
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
Without a foreign key property you have an Independent Association which requires that the object including all references must represent the state in the database before you change it. If you don't set the reference of SubObject in customer EF assumes that the original state in the database is that customer does not refer to any subobject. The generated SQL for the UPDATE statement contains a WHERE clause like this:
WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL
If the customer has a subobject in the DB [SubObject_Id] is not NULL, the condition is not fulfilled and the UPDATE does not happen (or happens for the "unexpected number of rows 0").
The problem does not occur if you have a foreign key property (Foreign Key Association): The WHERE clause in this case is only:
WHERE [Customers].[Id] = 1
So, it doesn't matter what's the original value of SubObject and of SubObjectId. You can leave the values null and the UPDATE works nonetheless.
Hence, the alternative solution to loading the original subobject is to introduce a foreign key property in Customer:
public int SubObjectId { get; set; }
Or, in case the relationship is not required:
public int? SubObjectId { get; set; }