MVC 4 - Entity Framework, ViewModel and Object Context - c#

I'm using Entity Framework with MVC 4 to develop a web application. I'm also using a ViewModel named VehicleTypeViewModel which is created like this :
public class VehicleTypeViewModel
{
public VehicleType VehicleType { get; set; }
public ProductType ProductType { get; set; }
[RegularExpression(#"^[a-zA-Zàéèêçñ\s][a-zA-Zàéèêçñ\s-]+$", ErrorMessage = "Invalid name !")]
public string Name { get; set; }
[Range(0, 300)]
public int CO2 { get; set; }
public List<SelectListItem> ProductCompanies { get; set; }
public List<SelectListItem> MotorTypes { get; set; }
}
In my Edit Action, everything's good but one thing : when I debug, arriving to the db.Attach(...) step, my app throws an exception which says :
The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state.
Here are my post action :
[HttpPost]
public ActionResult Edit(VehicleTypeViewModel vtvm)
{
ViewBag.Id_VehicleMotorType = new SelectList(db.VehicleMotorTypes, "Id_VehicleMotorType", "Name", vtvm.VehicleType.Id_VehicleMotorType);
ViewBag.Id_ProductCompany = new SelectList(db.ProductCompanies, "Id_ProductCompany", "Name", vtvm.ProductType.Id_ProductCompany);
vtvm.ProductCompanies = db.ProductCompanies.ToList().Select(c => new SelectListItem { Text = c.Name, Value = c.Id_ProductCompany.ToString() }).ToList();
vtvm.MotorTypes = db.VehicleMotorTypes.ToList().Select(v => new SelectListItem { Text = v.Name, Value = v.Id_VehicleMotorType.ToString() }).ToList();
VehicleType vehicleType = db.VehicleTypes.Single(v => v.Id_VehicleType == vtvm.VehicleType.Id_VehicleType);
ProductType productType = db.ProductTypes.Single(p => p.Id_ProductType == vtvm.ProductType.Id_ProductType);
VehicleMotorType vehicleMotorType = null;
ModelStateDictionary errors = Validator.isValid(vtvm.ProductType);
if (ModelState.IsValid)
{
if (errors.Count > 0)
{
ModelState.Merge(errors);
return View(vtvm);
}
productType.Model = vtvm.ProductType.Model;
productType.CatalogPrice = vtvm.ProductType.CatalogPrice;
productType.Id_ProductCompany = vtvm.ProductType.Id_ProductCompany;
if (!string.IsNullOrWhiteSpace(vtvm.Name) && (vtvm.CO2 > 0))
{
vehicleMotorType = new VehicleMotorType()
{
CO2 = vtvm.CO2,
Name = vtvm.Name
};
vehicleType.CO2 = vtvm.VehicleType.CO2;
vehicleType.VehicleMotorType = vehicleMotorType;
vehicleType.Id_ProductType = vtvm.ProductType.Id_ProductType;
}
else
{
vehicleType.CO2 = vtvm.VehicleType.CO2;
vehicleType.Id_ProductType = vtvm.ProductType.Id_ProductType;
vehicleType.Id_VehicleMotorType = vtvm.VehicleType.Id_VehicleMotorType;
}
db.VehicleTypes.Attach(vehicleType);
db.ProductTypes.Attach(productType);
db.ObjectStateManager.ChangeObjectState(vehicleType, EntityState.Modified);
db.ObjectStateManager.ChangeObjectState(productType, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vtvm);
}
I have no idea why I'm dealing with this kind of error. Any idea to solve this please?

Traffy,
Short answer, you should remove this 2 lines
db.VehicleTypes.Attach(vehicleType);
db.ProductTypes.Attach(productType);
When you retrieve any entity from an EntityFramework instance and modifies any property the Entity Framework is tracking the changes automatically.
To apply the changes to the database you just need to call
db.SaveChanges();
To understand better when to use Add and Attach methods you should read this
Entity Framework 4 - AddObject vs Attach.
I hope it helps.

Just remove the "Attach" as the object is already in the database, no need to attach again, just alter the state and save changes
var comp = (from com in db.tbl_CompGroup where com.Group_Id == GroupID select com).FirstOrDefault();
comp.Survey_Est = surveyPost.Survey_Est;
//db.tbl_CompGroup.Attach(comp);
db.ObjectStateManager.ChangeObjectState(comp, System.Data.EntityState.Modified);
db.SaveChanges();

Related

Find method not assigning values to object

I have a web project that everything is working and this below line works for other models except for this one. I'm just needing some info on where to start looking for the solution at.
When I debug it I see that it is getting all the new data that has been edited but it does not assign the new data to EditedtimeEntry. The EditedtimeEntry var has the old data not the new data that was edited. I looked at the timeEntry.Id and it has the new edit its just not being assigned to the EditedtimeEntry. There is no exception or build errors it just does not save the changes, and it looks like the reason it is not save the changes is because the EditedtimeEntry var is not getting the new data assigned to it for some reason. Can anyone point me in the right direction?
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Here is the Full method with the problem:
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
EditedtimeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
EditedtimeEntry.Company = comp;
db.Entry(EditedtimeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}
I have other methods for other models that are identical to this one and it works. Here is alsos the model
public class TimeEntry
{
[Key]
public Guid Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Description { get; set; }
public Rate Rate { get; set; }
public Technician Tech { get; set; }
public bool Paid { get; set; }
public Company Company { get; set; }
}
public enum Rate { Desktop, Network, Remote, Phone }
Thanks =)
Basically, what you are doing is exactly the contrary of what you want to do :) when you recover the item from the database, you are simply getting the item unchanged that is still in database. What you want is update the database item and then save changes (with EntityState.Modified then SaveChanges())
You simply want to edit timeEntry so that all changes done in the UI are translated into DB :
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
timeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
timeEntry.Company = comp;
db.Entry(timeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}

Create or update if item exist

I have a create form where if the specific Medicine exist, its number of supply will update or added with the new entry however if the specific Medicine doesn't exist, it will create a new batch of data.
Im having trouble at understanding how update works in MVC.
Here is the error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
Here is my controller:
public ActionResult Create([Bind(Include = "SupplyID,MedicineID,Expiration,NumberOfSupply")] Supply supply)
{
if (ModelState.IsValid)
{
bool supplyExsist = db.Supplies.Any(x => x.Expiration == supply.Expiration && x.MedicineID == supply.MedicineID);
if (supplyExsist)
{
var currentSupply = (from x in db.Supplies //get current supply
where x.MedicineID == supply.MedicineID
&& x.Expiration == supply.Expiration
select x.NumberOfSupply).First();
db.Entry(supply).State = EntityState.Modified;
supply.NumberOfSupply = currentSupply + supply.NumberOfSupply;
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
db.Supplies.Add(supply);
db.SaveChanges();
return RedirectToAction("Index");
}
}
ViewBag.MedicineID = new SelectList(db.Medicines, "MedicineID", "MedicineName", supply.MedicineID);
return View(supply);
}
Model:
public class Supply
{
[Key]
public int SupplyID { get; set; }
[ForeignKey("Medicine")]
public int MedicineID { get; set; }
public Medicine Medicine { get; set; }
[DataType(DataType.Date)]
public DateTime Expiration { get; set; }
[Display(Name = "Quantity")]
[Range(1, int.MaxValue, ErrorMessage = "The value must be greater than 0")]
public int NumberOfSupply { get; set; }
}
just try this
db.Supplies.AddOrUpdate(h => h.medicineID,supply));
it will check if there is a row with the same medicine ID in db if not it adds a new one else it updates it
You should change your if block with following :
if (supplyExsist)
{
var currentSupply = (from x in db.Supplies //get current supply
where x.MedicineID == supply.MedicineID
&& x.Expiration == supply.Expiration
select x.NumberOfSupply).First();
db.Supplies.Attach(supply);
db.Entry(supply).State = EntityState.Modified;
supply.NumberOfSupply = currentSupply + supply.NumberOfSupply;
db.SaveChanges();
return RedirectToAction("Index");
}

Entity Framework performing INSERT where it should not be

I have the following Entity Data models, simplified for brevity;
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
[Key]
public virtual T Id { get; set; }
}
public class User : Entity<int>
{
public List<Category> Categories { get; set; }
}
public class Category : Entity<int>
{
public string Description { get; set; }
}
public class List : Entity<int>
{
public Category Category { get; set; }
}
These are accessed from the DbContext using a DbContext exposed by either a generic service, or a more customised implementation to provide business logic.
When I publish the database and add the following code to the Seed() method, all is well and the data looks good directly in the database.
var user = new User
{
Email = "",
Categories = new List<Category>
{
new Category
{
Description = "Category 1",
},
new Category
{
Description = "Category 2",
}
}
};
context.Users.AddOrUpdate(u => u.Email, user);
var list = new List()
{
Id = 1,
Description = "Test List",
UserId = 1,
Category = user.Categories.FirstOrDefault()
};
context.Lists.AddOrUpdate(u =>u.Id, list);
Please note that the User owns the categories and you can (should only be able to) create them by accessing the Categories Property.
This gives me;
I am using these objects in my controller as such;
public ActionResult Edit(int id)
{
var categories = _usersService.GetUser(User.Id).Categories;
categories.Insert(0, new Category {Description = "", Id = 0});
var list = _listsService.GetList(id);
var viewModel = new EditViewModel
{
Id = list.Id,
Reference = list.Reference,
Description = list.Description,
CategoryId = list.Category?.Id ?? 0,
Categories = new SelectList(categories.AsEnumerable(), "Id", "Description")
};
return View(viewModel);
}
In the above test, I am using the List inserted during the Seed and I can see the List does indeed have a Category, and the values are correct.
For information, I am using the following ViewModel. I have been investigation methods to be able to 'select' the User.Categories from a DropDown and this appeared to work the best at present.
public class EditViewModel
{
[Required]
public int Id { get; set; }
[Required]
public string Description { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
[ScaffoldColumn(false)]
public int CategoryId { get; set; }
[ScaffoldColumn(false)]
public Guid Reference { get; set; }
}
The populated ViewModel looks like this;
and finally, the POST method;
[HttpPost]
public ActionResult Edit(EditViewModel model)
{
if (ModelState.IsValid)
{
var categories = _usersService.GetUser(User.Id).Categories;
var list = _listsService.GetList(model.Id);
list.Description = model.Description;
list.Category = categories.FirstOrDefault(x => x.Id == model.CategoryId);
_listsService.Update(list);
categories.Insert(0, new Category { Description = "", Id = 0 });
model.Categories = new SelectList(categories.AsEnumerable(), "Id", "Description");
return View(model);
}
return View(model);
}
So, in the following scenarios, this is what happens. For clarity, each time I am doing this, I go back to the Lists Index and GET Edit again;
Select '' from the Dropdown - NO Categories INSERT,UPDATE on Lists table only, setting [Category_Id] = NULL - Correct
SELECT 'Category 1' from DropDown. INSERT categories, UPDATE lists - NOT Correct
The code being used to update the List is;
public void Update(T entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
Now, I know this is something I am doing, but being new to EF, I have no idea.
The problem was down to how the values were being set.
I needed to set the Foreign Key and assign the value to this.
public class List : Entity<int>
{
public int CategoryId { get; set; }
[ForeignKey("CategoryId")
public Category Category { get; set; }
}
and the value was then set as
var list = _listsService.GetList(model.Id);
list.Description = model.Description;
list.CategoryId = model.CategoryId;
list.Category = null;
_listsService.Update(list);
After this, when getting the list from the repository, both the Category and CategoryID would be populated correctly.
The issue was down the setting the Entity as modified, this internally indicated that the Category was 'new' when in fact it was not. You could also 'attach' and existing category to the entity/context but decided the method above was better.
A slightly better approach to the above would be to create a new 'UpdateList' method which could be called rather than the generic update. This method would perform the setting of the relevant properties outside of the controller method.
I am not sure but possible you must write your update method as follow:
public void Update(T entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
var item = _collection.Find(item.Id);
if (item == null) throw new ArgumentNullException(nameof(item ));
_context.Entry(item).CurrentValues.SetValues(entity);
_context.SaveChanges();
}
The problem can be in view, of you don't have the I'd the EF will add new record instead of update the existing record.
In general: Watch out for existing classes when you name your custom class (like List):
public class List : Entity<int>
{
public Category Category { get; set; }
}
Be shoure that you use the right class in the right namespace.
var user = new User
{
Email = "",
Categories = new YourOwnNamespace.List<Category>
{
new Category
{
Description = "Category 1",
},
new Category
{
Description = "Category 2",
}
}
};
Avoid naming classes and properties to existing names. Beter change List class in e.g. 'MyList'.

C# - The entity or complex type cannot be constructed in a LINQ to Entities query

I'm trying to send data with a view and print it out, but I'm struggling really hard since I'm very new to C#.
So here's my model ViewModel:
namespace P104.Models
{
public class ViewModel
{
}
public class Location
{
public int loc_id { get; set; }
public string loc_name { get; set; }
}
public class Competentie
{
public int Comp_id { get; set; }
public string competentie { get; set; }
}
public class MyViewModel
{
public User User { get; set; }
public IEnumerable<Locations> Locations { get; set; }
public IEnumerable<Competenties> Competenties { get; set; }
}
}
This is the function I have in the controller
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User user = db.user.Find(id);
if (user == null)
{
return HttpNotFound();
}
var competenties =
from usercomp in dbE.UserComp
join comp in dbE.Competenties on usercomp.fk_Comp_id equals comp.comp_id
where usercomp.fk_user_id == id
select new Competenties { competentie = comp.competentie };
var locations =
from userloc in dbE.UserLoc
join loc in dbE.Locations on userloc.fk_location_id equals loc.loc_id
where userloc.fk_user_id == id
select new Locations { loc_name = loc.loc_name };
var model = new MyViewModel
{
User = user,
Locations = locations.ToList(), // eagerly fetch the data that will be needed in the view
Competenties = competenties.ToList(), // eagerly fetch the data that will be needed in the view
};
return View(model);
}
And he's how I try to print it out in the view:
#foreach (var location in Model.Locations)
{
<dt>#location.locname</dt>
<dd>#location.locname</dd>
}
#foreach (var competentie in Model.Competenties)
{
<dt>#competentie.competentie</dt>
<dd>#competentie.competentie</dd>
}
I always recevie this error
The entity or complex type 'P104.Models.Locations' cannot be
constructed in a LINQ to Entities query.
I've found a few solutions, but I'm struggling to apply them to my code so they don't work.
Thanks in advance for your help
You seem to have got a typo here:
select new Locations { loc_name = loc.loc_name };
This should be:
select new Location { loc_name = loc.loc_name };
The model you are projecting against is called Location, not Locations. It's unclear what the Locations model is since you haven't shown that in your question.
And of course adapt your view accordingly:
#foreach (var location in Model.Locations)
{
<dt>#location.loc_name</dt>
<dd>#location.loc_name</dd>
}
By the way I will recommend you following standard C# naming conventions. This convention dictates that in C# a property name should start with an uppercase letter and not contain _. So basically you would rather use LocationName or just Name instead of loc_name. Same remark for your Competentie model.

Entity Framework creating duplicate objects from list when object has composite Primary Key

I have an object with a child collection as such:
public class ClaimGroup : BaseModel
{
public int Id { get; set; }
[Required,StringLength(100)]
public string Name { get; set; }
public int Order { get; set; }
public bool Include { get; set; }
public virtual ICollection<ClaimGroupItem> Items { get; set; }
}
The ClaimGroupItem is:
public class ClaimGroupItem : BaseModel
{
[Key,Column(Order = 0),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ClaimGroupId { get; set; }
[ForeignKey("ClaimGroupId")]
public virtual ClaimGroup ClaimGroup { get; set; }
[Key,Column(Order = 1),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int MenuItemId { get; set; }
[ForeignKey("MenuItemId")]
public virtual MenuItem MenuItem { get; set; }
public string ClaimValue { get; set; }
}
As you can see, it has a composite primary key: MenuItemId and ClaimGroupId.
On updating, it creates duplicates of the ClaimGroupItem objects, with POCO objects being set correctly, but then it creates dynamic proxy items with the same values, hence duplicating the objects.
E.g.:
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
The items collection above containts 10 ClaimGroupItemViewModel objects as shown in the image below.
However, when I map the viewModel objects to the Model objects, the collection then has 20 objects, 10 of which are proxy items as seen below:
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
Then when the object goes to save, I get the following error:
_repository.Update<ClaimGroup>(itemToSave);
Violation of PRIMARY KEY constraint 'PK_dbo.ClaimGroupItems'. Cannot
insert duplicate key in object 'dbo.ClaimGroupItems'. The duplicate
key value is (20, 6). The statement has been terminated.
The error makes sense, EF is trying to save 10 duplicate objects.
Why is Entity Framework creating 10 new objects and hence duplicates?
Here is the code on POST that gets the whole list of 79 items in viewModel.Items, then we select just the ones with claimvalue not null. There are NO duplicates at this stage.
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
// There is just 10 items in items variable at this point
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItem = new MenuItem { Id = i.MenuItemId}
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
If you don't need Proxies, during construction of your DBContext set ProxyCreationEnabled = false and check. I have seen cases when we don't even need Proxies and EF by default creates one for all Entities.
I finally got it working.
It was as simple as calling the
itemToSave.Items.Clear()
method to make sure it doesn't load the old proxy objects. What a pain that was to figure out! Here is my working code. Thanks for everyone's help.
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
itemToSave.Items.Clear();// This needs to be done, otherwise it will try and load the list from the DB again.
itemToSave.Items = (from i in viewModel.Items
where !string.IsNullOrEmpty(i.ClaimValue)
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}

Categories