I have an entity that I am updating and the method in the controller has these lines in it;
db.Entry(userdetails).State = EntityState.Modified;
try {
db.SaveChanges();
}
I think this is fine, however the entity has a collection in it and these records needs to be created, not updated. I am therefore getting this 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. Refresh ObjectStateManager entries."
I have read that this may be because my AppUserInfo objects have an id of 0 because they need to be added.
The class for the outer entity looks like this;
public class User {
public Guid UserId { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public List<AppUserInfo> InfoList { get; set; }
}
Now in this situation for example, the phone number could have been modified, and the 'InfoList' collection has new items in that need to be created. Some may need to be updated as well. The 'AppUserInfo' class looks like this;
public class AppUserInfo
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AppUserInfoId { get; set; }
public string info { get; set; }
}
How do I handle this in EF? Am I allowed to save collections in this way? I do not know how to say that there are modifications and additions and possible deletions in the list to EF. Do I simply just remove the line;
db.Entry(userdetails).State = EntityState.Modified;
Any info on the correct way to do this would be very helpful.
Thanks,
db.Entry(userdetails).State = EntityState.Modified;
This line of code is used for editing your objects, if you have no trouble creating your User then all you need is another controller to handle AppUserInfo
Lets imagine that you have a AppUserInfo controller, and you have a AppUserInfo viewModel somewhere. Then you can somthing like the following to accomplish what you want to do.
Your view model like this:
puclic class AppUserInfoCreateViewModels
{
public Guid UserId { get; set; }
public string Info { get; set; }
}
Then your Controller like this:
public class AppUserInfoController : Controller
{
private readonly IYourDataSource _db;
public AppUserInfoController(IYourDataSource db)
{
_db = db;
}
[HttpGet]
public ActionResult Create(int userId)
{
var model = new AppUserInfoCreateViewModels();
model.UserId = userId;
return View(model);
}
[HttpPost]
public ActionResult Create(AppUserInfoCreateViewModels viewModel)
{
if(ModelState.IsValid)
{
var user = _db.Users.Single(d => d.UserId == viewModel.UserId);
var appUserInfo= new AppUserInfo();
appUserInfo.Info= viewModel.Infor;
user.AppUserInfos.Add(appUserInfo);
_db.Save();
return RedirectToAction("detail", "user", new {id = viewModel.UserId});
}
return View(viewModel);
}
}
I hope this helps, ask any questions you may have
Related
I am working on a project that uses an MVC API with Entity Framework 6.1.3. We need to implement Get, GET/ID, Insert, Update and Delete API's.
for Insert and Update we have a parameter class that is not the same one as the DB, i am not sure what will be the best solution for that.
For an Update (the one that i have more questions) i can find a record and then update all properties manually, this is something i want to avoid. If i use the currentvalues from the entity then i will have to set the ExtraValues properties in all the apis that i am going to write, that kind of looks weird.
Note: i want to have a child class since most of the entities uses the same fields (Created/Updated) so instead of having those in all the classes i rather have them as inheritance.
There has to be a better solution for this problem, could someone help with ideas or best ways to do this.
public class DBClassA : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
public class DBClassB : ExtraValues
{
public int ID { get; set; }
public string Name { get; set; }
}
//This will be use in all the classes
public class ExtraValues
{
public string SameValueInOtherClasses { get; set; }
public string SameValueInOtherClasses2 { get; set; }
public string SameValueInOtherClasses3 { get; set; }
}
[HttpGet]
public List<DBClassA> Get()
{
return new List<DBClassA>();
}
[HttpGet]
public DBClassA Get(int ID)
{
return new DBClassA();
}
[HttpPost]
public HttpResponseMessage Insert(DBClassA obj)
{
using (var context = new DBEntities())
{
context.Entity.Attach(DBClassA);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
[HttpPut]
public HttpResponseMessage Update(int ID, DBClassA obj)
{
using (var context = new DBEntities())
{
var entity = context.Entity.Find(ID);
//I will have to put the ExtraValues here
obj.ExtraValues = "";
_context.Entry(entity).CurrentValues.SetValues(obj);
context.SaveChanges();
}
return Request.CreateResponse(HttpStatusCode.OK);
}
using (var db = new DBEntities())
{
db.Entry(obj).State = EntityState.Modified;
db.SaveChanges();
}
more info: http://www.entityframeworktutorial.net/EntityFramework4.3/update-entity-using-dbcontext.aspx
I'm trying to list the items from my database into my view but I'm getting null back.
I know the connection must be working to a certain extent because in my database the tables didn't exist but once I ran my program it did create the tables. However when I add content into my table my view still returns NULL.
Also, haven't touched the Review table yet, just worried about getting Restaurants working.
Restaurant.cs
namespace OdeToFood.Models
{
public class Restaurant
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string Country { get; set; }
public ICollection<RestaurantReview> Reviews { get; set; }
}
}
OdeToFood.cs
namespace OdeToFood.Models
{
public class OdeToFoodDb : DbContext
{
public DbSet<Restaurant> Restaurants { get; set; }
public DbSet<RestaurantReview> Reviews { get; set; }
}
}
Controller
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View();
}
Index.cshtml
#model IEnumerable<OdeToFood.Models.Restaurant>
#{
ViewBag.Title = "Home Page";
}
#{
if (Model != null)
{
foreach (var item in Model)
{
<div>
<h4>#item.Name</h4>
<div>#item.City, #item.Country</div>
<hr />
</div>
}
}
else
{
<h1>Null</h1>
}
}
You need to pass to model back to the view.
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View(model);
}
You never actually send the model to the view. Pass it as an argument:
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View(model);
}
Additionally, it's generally a good idea not to create database contexts in a shared scope. Keep the context as close to where it's used as possible and only expand its scope when you really need to. Something like this:
public ActionResult Index()
{
using (var _db = new OdeToFoodDb())
{
var model = _db.Restaurants.ToList();
return View(model);
}
}
Database contexts/connections in a shared scope is just asking for problems unless you pay close attention to what you're doing. As the code gets more complex, it becomes more likely that other methods will try to use it and it may be in an unknown state at that time.
Example:
I have table Orders and table OrderPositions.
public partial class Orders
{
public Orders()
{
this.OrderPositions = new HashSet<OrderPositions>();
}
public int OrderId { get; set; }
public string Title { get; set; }
public virtual ICollection<OrderPositions> OrderPositions { get; set; }
}
public partial class OrderPositions
{
public int OrderPositionId { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public virtual Orders Orders { get; set; }
}
On the view user can modify single record from OrderPositions table.
In controller:
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
So parameter orderPosition.Orders should be = null because on the form in view user can modify only order position. But can user hack it? I mean that in parameter orderPosition.Orders won't be null and I update record not only in table OrderPositions but also in table Orders? Or ASP.NET MVC prevent from that situation?
It really depends on what you do here
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
// save orderPosition
}
If you're saving the whole entity then yes there is nothing stopping a user passing over addition entity properties. There are a few ways to prevent this though, here are a couple...
1.Create a new entity at the point of saving
[HttpPost]
public ActionResult Edit(OrderPositions orderPosition)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = orderPosition.OrderPositionId,
OrderId = orderPosition.OrderId,
Name = orderPosition.Name
};
//Then save this new entity
}
}
2.Create a Model specific to the entity's action
public class EditOrderPosition
{
[Required]
public int PositionId { get; set; }
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
[HttpPost]
public ActionResult Edit(EditOrderPosition model)
{
if(ModelState.IsValid)
{
var order = new OrderPositions
{
OrderPositionId = model.PositionId,
OrderId = model.Id,
Name = model.Name
};
//Then save this new entity
}
}
I generally go with the 2nd method as it stops direct user involvement with my entities. As a rule of thumb I never use entity objects as parameters in controller actions.
Hope this helps
Yes they can. This is one reason I do not expose my entities as a parameter to action methods, instead I use DTOs that only have the properties that I expect.
This is an example of the Mass Assignment Vulnerability.
Yes, there is nothing preventing a rogue app calling your endpoint with arbitrary data. Always validate everything serverside.
I'm trying to insert a new Entity in EF:
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
And I receive from the View the model like this (example values):
kms = 222
Model.Id = 3
Then when I do the Add and SaveChanges of the Entity Framework, I get a NULL record inserted in the Model Table (that generated a new ID) and a record in the Ad Table with the new inserted Model Id.
Why is this happening?
Service Layer:
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_adRepository.Add(adDomain);
_adRepository.Save();
}
Repository:
public void Add(T entity)
{
_dbSet.Add(entity);
}
public void Save()
{
try
{
_dataContext.SaveChanges();
}
catch (DbEntityValidationException e)
{
var s = e.EntityValidationErrors.ToList();
throw;
}
}
ViewModel:
public class CreateAdViewModel
{
// Primary properties
public string Version { get; set; }
public int Kms { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
Dto:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
public int Model_Id { get; set; }
}
The Mapping:
Mapper.CreateMap<CreateAdDto, Ad>().ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
If you just call the Add method on the Ad instance, all the related entities are treated as new entities. Therefore you can attach the Model instance first and add the Ad.
context.Models.Attach(ad.Model);
context.Ads.Add(ad);
context.SaveChanges();
SOLUTION BASED ON #Eranga Answer (thanks man!)
Hope this can help somebody else as it help me thanks to #Eranga.
What it was happening is that in the Mapping from DTO to DOMAIN, the Model entity was maping from an Model_Id coming from a Dropdownlist in the View to an Entity Model (as you can see in the mapping line in the question).
Then, when it was added to the database trough Entity Framework, the EF was not aware of the existence of the Model navigation property in the Ad Domain Entity.
So what I had to create to solve this was adding a new method to my repository to handle the possibility to attach the Model Entity to the Ad Entity context:
public void Attach(T entity)
{
_dbSet.Attach(entity);
}
and ad the attach in the Ad Service Create method:
private readonly IRepository<Ad> _adRepository;
private readonly IRepository<Search> _searchRepository;
private readonly IRepository<Model> _modelRepository;
public AdService(IRepository<Ad> adRepository, IRepository<Search> searchRepository, IRepository<Model> modelRepository)
{
_adRepository = adRepository;
_searchRepository = searchRepository;
_modelRepository = modelRepository;
}
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_modelRepository.Attach(adDomain.Model);
_adRepository.Add(adDomain);
_adRepository.Save();
}
So now, when the adDomain object arrives to the EF trough the _adRepository.Add, it already knows about the existence of the navigation property Model, and can add the new adDomain ojbect.
Before, what it was happening is that, when the adDomain object was arriving at the EF, the EF was not aware of the Model existence so it was creating an null record.
Hope this help anybody else.
Regards.
Let's say you have an object called Person that looks like this:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int NumberOfCatsNamedEnder { get; set; }
}
I have a simple HTML form that exposes the properties that gets posted to an ASP.NET MVC action inside of my PersonController class. The issue I have is that if someone puts in the letter 'A' for NumberOfCatsNamedEnder, I get a The model of type 'Person' was not successfully updated. error. Since this happens while trying to update the Model, I can't find any way to check to see if someone passed in a non-integer value without resorting to
if(!IsInteger(formCollection["NumberOfCatsNamedEnder"]))
{
ModelState.AddModelError(
"NumberOfCatsNamedEnder",
"Ender count should be a number");
}
Is there a better way to do this? I was able to find some information on custom ModelBinders; is that what is needed?
I really like the approach of using a presentation model. I'd create a class like this:
class PersonPresentation
{
public int ID { get; set; }
public string Name { get; set; }
public string NumberOfCatsNamedEnder { get; set; }
public void FromPerson(Person person){ /*Load data from person*/ }
}
Then your controller action can bind the view to a PersonPresentation:
public ActionResult Index()
{
Person person = GetPerson();
PersonPresentation presentation = new PersonPresentation();
ViewData.Model = presentation.FromPerson(person);
return View();
}
...and then accept one in your Update method and perform validation:
public ActionResult Update(PersonPresentation presentation)
{
if(!IsInteger(presentation.NumberOfCatsNamedEnder))
{
ModelState.AddModelError(
"NumberOfCatsNamedEnder",
"Ender count should be a number");
}
...
}