I'm trying to develop my first ASP.net MVC project with Entity Framework. I wasn't using any view models, i was using same models for both in views and database transactions. Here is the thing, i have custom user table. In creation, i have 5 things in my User model: UserName, UserPassword, FullName, Branch and BranchId(Navigation to another table). But when i want to edit a user, i don't need the UserPassword field, because changing the password won't be possible for now. So i created a model same with the User model except the UserPassword field named UserEdit.
In create view i use my User model, in edit view i use the UserEdit model. In controller i'm using automapper and copy values from User to UserEdit and return that to view. It's working fine, problem is about updating.
I'm trying to update the user like this:
public bool Update(UserEdit userEdit) {
User user = Find(userEdit.UserUsername);
Mapper.CreateMap<UserEdit, User>();
user = (User)Mapper.Map(userEdit, user, typeof(UserEdit), typeof(User));
if (_modelState.IsValid)
{
_transactionManager.GetContext().Entry(user).State = System.Data.Entity.EntityState.Modified;
_transactionManager.CommitTransaction();
return true;
}
else
{
return false;
}
}
But it gives me this error:
Additional information: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
When i check the user i'm trying to update, i see Entity Framework related objects in it. So i'm thinking reason of this error is probably about branch table and those related objects in the user object. I really don't need those entity related objects in my user object. If i could only copy the properties in the model, it would be great. Maybe i'm doing something else wrong. Because i know people use view models all the time, there should be an easy way to do it.
Any ideas?
Thanks
You really shouldn't be rolling your own password authentication. Use what's built into MVC. That said, the easiest way to save your view model would be this;
using (YourContext db = new YourContext())
{
User u = db.User.Find(userEdit.UserUsername);
db.Entity(u).State = System.Data.Entity.EntityState.Modified;
u.FullName = userEdit.FullName;
....set the other properties here...
db.SaveChanges();
}
Related
Some 2 years+ ago I asked this question which was kindly solved by Steve Py.
I am having a similar but different problem now when mapping with sub-objects. I have had this issue a few times and worked around it, but facing doing so again, I can't help thinking there must be a more elegant solution. I am coding a memebership system in Blazor Wasm and wanting update membership details via a web-api. All very normal.
I have a library function to update the membership:
public async Task<MembershipLTDTO> UpdateMembershipAsync(APDbContext context, MembershipLTDTO sentmembership)
{
Membership? foundmembership = context.Memberships.Where(x =>x.Id == sentmembership.Id)
.Include(x => x.MembershipTypes)
.FirstOrDefault();
if (foundmembership == null)
{
return new MembershipLTDTO { Status = new InfoBool(false, "Error: Membership not found", InfoBool.ReasonCode.Not_Found) };
}
try
{
_mapper.Map(sentmembership, foundmembership, typeof(MembershipLTDTO), typeof(Membership));
//context.Entry(foundmembership).State = EntityState.Modified; <-This was a 'try-out'
context.Memberships.Update(foundmembership);
await context.SaveChangesAsync();
sentmembership.Status = new InfoBool(true, "Membership successfully updated");
return sentmembership;
}
catch (Exception ex)
{
return new MembershipLTDTO { Status = new InfoBool(false, $"{ex.Message}", InfoBool.ReasonCode.Not_Found) };
}
}
The Membership object is an EF DB object and references a many to many list of MembershipTypes:
public class Membership
{
[Key]
public int Id { get; set; }
...more stuff...
public List<MembershipType>? MembershipTypes { get; set; } // The users membership can be several types. e.g. Employee + Director + etc..
}
The MembershipLTDTO is a lightweight DTO with a few heavy objects removed.
Executing the code, I get an EF exception:
The instance of entity type 'MembershipType' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I think (from the previous question I asked some time ago) that I understand what is happening, and previously, I have worked around this by having a seperate function that would in this case update the membership types. Then, stripping it out of the 'found' and 'sent' objects to allow Mapper to do the rest.
In my mapping profile I have the mappings defines as follows for these object types:
CreateMap<Membership, MembershipLTDTO>();
CreateMap<MembershipLTDTO, Membership>();
CreateMap<MembershipTypeDTO, MembershipType>();
CreateMap<MembershipType, MembershipTypeDTO>();
As I was about to go and do that very thing again, I was wondering if I am missing a trick with my use of Mapper, or Entity Framework that would allow it to happen more seamlessly?
A couple of things come to mind. The first thing is that the call to context.Memberships.Update(foundmembership); isn't required here so long as you haven't disabled tracking in the DbContext. Calling SaveChanges will build an UPDATE SQL statement for whatever values change (if any) where Update will attempt to overwrite the entitiy(ies).
The issue you are likely encountering is common when dealing with references, and I would recommend a different approach because of this. To outline this, lets look at Membership Types. These would typically be a known list that we want to associate to new and existing memberships. We're not going to ever expect to create a new membership type as part of an operation where we create or update a membership, just add or remove associations to existing memberships.
The problem with using Automapper for this is when we want to associate another membership type in our passed in DTO. Say we have existing data that had a membership associated with Membership Type #1, and we want to add MemberShip Type #2. We load the original entity types to copy values across, eager loading membership types so we get the membership and Type #1, so far so good. However, when we call Mapper.Map() it sees a MemberShip Type #2 in the DTO, so it will add a new entity with ID #2 into the collection of our loaded Membership's Types collection. From here, one of three things can happen:
1) The DbContext was already tracking an instance with ID #2 and
will complain when Update tries to associate another entity reference
with ID #2.
2) The DbContext isn't tracking an instance, and attempts to add #2
as a new entity.
2.1) The database is set up for an Identity column, and the new
membership type gets inserted with the next available ID. (I.e. #16)
2.2) The database is not set up for an Identity column and the
`SaveChanges` raises a duplicate constraint error.
The issue here is that Automapper doesn't have knowledge that any new Membership Type should be retrieved from the DbContext.
Using Automapper's Map method can be used to update child collections, though it should only be used to update references that are actual children of the top-level entity. For instance if you have a Customer and a collection of Contacts where updating the customer you want to update, add, or remove contact detail records because those child records are owned by, and explicitly associated to their customer. Automapper can add to or remove from the collection, and update existing items. For references like many-to-many/many-to-one we cannot rely on that since we will want to associate existing entities, not add/remove them.
In this case, the recommendation would be to tell Automapper to ignore the Membership Types collection, then handle these afterwards.
_mapper.Map(sentmembership, foundmembership, typeof(MembershipLTDTO), typeof(Membership));
var memberShipTypeIds = sentmembership.MembershipTypes.Select(x => x.MembershipTypeId).ToList();
var existingMembershipTypeIds = foundmembership.MembershipTypes.Select(x => x.MembershipTypeId).ToList();
var idsToAdd = membershipTypeIds.Except(existingMembershipTypeIds).ToList();
var idsToRemove = existingMembershipTypeIds.Except(membershipTypeIds).ToList();
if(idsToRemove.Any())
{
var membershipTypesToRemove = foundmembership.MembershipTypes.Where(x => idsToRemove.Contains(x.MembershipTypeId)).ToList();
foreach (var membershipType in membershipTypesToRemove)
foundmembership.MembershipTypes.Remove(membershipType;
}
if(idsToAdd.Any())
{
var membershipTypesToAdd = context.MembershipTypes.Where(x => idsToRemove.Contains(x.MembershipTypeId)).ToList();
foundmembership.MembershipTypes.AddRange(membershipTypesToAdd); // if declared as List, otherwise foreach and add them.
}
context.SaveChanges();
For items being removed, we find those entities in the loaded data state and remove them from the collection. For new items being added, we go to the context, fetch them all, and add them to the loaded data state's collection.
Notwithstanding marking Steve Py's solution as the answer, because it is a solution that works, though not as 'elegant' as I would have liked.
I was pointed in another direction however by the comment from
Lucian Bargaoanu, which, though a little cryptic, after some digging I found could be made to work.
To do this I had to add 'AutoMapper.Collection' and 'AutoMapper.Collection.EntityFrameworkCore' to my solution. There was a bit of jiggery pokery around setting it up as the example [here][2], didn't match up with my set up. I used this in my program.cs:
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
mc.AddCollectionMappers();
});
I also had to modify my mapping profile for the object - DTO mapping to this:
//Membership Types
CreateMap<MembershipTypeDTO, MembershipType>().EqualityComparison((mtdto, mt) => mtdto.Id == mt.Id);
Which is used to tell AutoMapper which fields to use for an equality.
I took out the context.Memberships.Update as recommended by Steve Py and it works.
Posted on behalf of the question asker
Say I have an User entity and it haves a Password property which is not nullable:
Map((x) => x.Password).Column("PASSWORD").Not.Nullable();
In the create action, I manually set the Password value as it is a generated hash. It never goes to the View.
In the update, I try to save it, but I don't have the Password value. I get this error for Password propery:
PropertyValueException: not-null property references a null or transient value
This is my Update method:
public bool Update(UserViewModel input)
{
if (!IsValid(input))
return false;
var user = Mapper.Map<User>(input);
this.UserRepository.Update(user); // <- this is a wrapper for NH's Session.Update()
return true;
}
How can I tell NHibernate to ignore a property in an update?
Note: This is not the same as this question.
Update:
Here is how I use it: The Password property never goes to any View. Even in the Login action I have a generic LoginViewModel, only for it's view. That property is only used in the login process and it could be updated in the Reset password feature, where a new password is generated and sent to the related user e-mail.
I see 2 possibilities to achieve that
Get the entity before Update and update explicitly
// use 'Get()' because it uses the NHibernate cache
// if you already loaded the entity, it won't query the db and read it from the cache
var user = this.UserRepository.Get(input.Id);
user.PropertyToUpdate = ...;
this.UserRepository.Update(user);
In addition to that, you can use Dynamic-Update. But this will only work with entities that are bound to the Session. NHibernate will then only update the changed properties and not all while you are updating a entity. Otherwise NHibernate can't know which properties has changed and will update all. DynamicUpdate should only work when you got the entity from NHibernate. The Entity is then bound to the Context and NHibernate can track changes.
If all your entities are auto mapped you can use a ClassConvention to set DynamicUpdate to all your entities (or just filter the ones you want):
public class ClassConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.DynamicUpdate();
}
}
As another option you can use a explicit mapping override:
public class UserOverride : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.DynamicUpdate();
}
}
Use different classes for different behaviours
You can declare different classes for the same Entity. One class for User creation or password resetting that contains the password property. And one class for simple updates that don't need the password property. FluentNhibernate allows you to map different classes for the same table. But you need a little more effort in mapping or rather in AutoMappingOverrides.
An issue occurs when I want to save an object posted back from a form, but exclude a particular field from the resultant SQL statement. In this case I did not want to include the password field in a user edit form.
An edit HttpPost controller method contained this:
db.Users.Attach(user);
db.Entry(user).State = EntityState.Modified;
db.Entry(user).Property(x => x.Password).IsModified = false;
db.SaveChanges();
I assumed that the .IsModified statement would mean that the generated SQL would consist of an UPDATE statement without the password field and so not overwrite the existing password value with null in the database.
However, this was not the case and EF seems to work in a slightly different way. My user class and its associate metadata class did not specify that the password field was required and yet, when executing, there was a ‘Password field required’ DbEntityValidationException thrown when SaveChanges was invoked.
One solution would be to include the password field with its value as a hidden form field, which would suffice in most cases, but in this instance would not be a good idea as password, hashed or otherwise is rather sensitive data.
On further inspection, it seemed that, in this case where we are using a database first approach, the .edmx file contains a ‘not null’ directive on the Password field. When EF comes to execute the actual save, this is checked and the exception raised.
This was not entirely intuitive in my opinion but once it is known it can be worked around.
My solution was to provide a value for the excluded field and retain the .IsModified statement. This means the DbEntityValidationException isn’t thrown as EF sees the Password field containing a value (as prescribed in the .edmx file) but is not actually written to the database due to IsModified being false. E.g.
user.Password = "not required";
db.Users.Attach(user);
db.Entry(user).State = EntityState.Modified;
db.Entry(user).Property(x => x.Password).IsModified = false;
db.SaveChanges();
Is there a better solution to this as using a dummy value as above seems like a bit of a hack?
You haven't really explained why you don't want to save the password field, I'm assuming its just because you don't know what the value is and in your current code it is being set to NULL.
You could use a ViewModel to bind with your webpage, and only supply the fields that are required (so in your case, NOT the password field). When the user POSTs data back, you can load the user from the db, set only the fields you need to, then save the data back. The password field will be unchanged.
For example, assuming the User table is this:
ID
UserName
Password
FullName
You would have a view model class of
public class UserViewModel
{
public int ID { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
}
When the data is posted back to the server from the client:
using (MyEntity db = new MyEntity())
{
User u = db.Users.Find(userViewModel.ID);
u.UserName = userViewModel.UserName;
u.FullName = userViewModel.FullName;
db.SaveChanges();
}
GO with the stored procedure approach, where you dont need to worry about entity framework, call stored proc from entity framework and update your table inside sql statement without updating password field
I'm very new to ASP.NET MVC. I'm referring this answer to solve my problem. I researched a lot for hours to find a way to exclude properties from edit action.
I used data binging but with no luck, i used a view model to include only the fields need to be modified, which worked but I'm not 100% happy because I'm looking for an easier and a reusable approach. I tried using TryUpdateModel() but i don't completely understand how to implement it.
Above link looks like a perfect solution but unfortunately i still got the same validation error Cannot insert the value NULL into column 'CreatedBy', table 'xxx'; column does not allow nulls. UPDATE fails.
Any idea why? help greatly appreciated.
Edit : let me clearly state what i want here again. I want 'CreatedBy' in my model that's why i have a property for that. I cant just remove it. I don't want to use a hidden field in my view to pass the value so it won't be null. I just want a proper and easy way to exclude properties from Edit. Is there a better solution for this except using viewmodels?
Details:
CreatedBy is a required field which is populated on create action.
My view contains editors for all the fields in my model except CreatedBy
My controller:
[HttpPost]
[ValidateOnlyIncomingValuesAttribute]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Exclude = "CreatedBy")] Request model)
{
if (ModelState.IsValid)
{
var currentUser = User.Identity.Name;
var emp = db.Employees.SingleOrDefault(e => e.ApplicationUser == db.Users.FirstOrDefault(u => u.UserName.Equals(currentUser)));
if (emp != null)
{
model.ModifiedBy = emp.NIC;
model.ModifiedDate = DateTime.Now;
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
// some code
}
}
My action filter
public class ValidateOnlyIncomingValuesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var valueProvider = filterContext.Controller.ValueProvider;
var keysWithNoIncomingValue = modelState.Keys.Where(x => !valueProvider.ContainsPrefix(x));
foreach (var key in keysWithNoIncomingValue)
modelState[key].Errors.Clear();
}
}
As manish indicated, your error has nothing to do with validation
Option 1
Render a hidden input for CreatedBy so it posts back and remove the [Bind(Exclude = "CreatedBy")] from your method. The value will be updated in the database, but with the same value you started with so its unchanged
Option 2
Depending on which version of EF you can use a 'blacklist' approach (for EF5+ I think)
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.CreatedBy).IsModified = false;
db.SaveChanges();
or a 'whitelist' approach where you need to set EntityState.Modified; for each property except CreatedBy
// db.Entry(model).State = EntityState.Modified; // do do this
db.Entry(model).Property(x => x.SomeProperty).State = EntityState.Modified;
// Repeat for all properties except `CreatedBy`
This is not MVC validation error. it is exception you getting. you are trying to add null value for CreatedBy column which is actually non-nullable.
Make CreatedBy column nullable or assign value for it from controller.
//Update
Understanding your decision not to use ViewModels, even that for me that's not for the sake of reusability, I think there are two things clear here.
A-> You are not having a validation error
B-> [Bind(Exclude = "CreatedBy")] works fine
So per A, I would even remove that filter. You're sure that that CreatedBy property has been created according to the validation rules so you don't need to skip validation in that given you are expecting that value again. And if a dodgy user has tried to edit it that validation at least doesn't make you any harm.
B-> You have that model with that property as null so as stated in a few comments you should be populating it before updating the entitiy in the db. But you don't trust the hidden input fields as the value for that property.
I guess you're using an ORM and I don't know if any allows you to update partially the way you're trying to do i.e. just passing a model with some null property in a non-nullable one and expecting it to infere that it has to get the previous value for that property. I hardly doubt it. So the options here aren't that many:
You should fetch the model from the database, update individually the subset of properties you want to and save it again. So that means basically you could even get rid of The Bind(Exclude... bit, you won't give and editor field for that in the view and in case somebody edit it and won't use that update.
I wouldn't even think of doing that in a so-called reusable filter as the trip to the db would be per case.
I can think of some other convoluted ways of doing it. But don't worth mentioning them, actually this leaves a question open to me, are you exposing theId for that entity? because you are opening a vulnerability there, they can edit another one and maybe that's not what you want. and so on... I'm not sure if you came across thisin your research.
To me, I insist you shouldn't be messing around like this: ViewModels is the answer
//End of update
I would create a ViewModel with the properties you want in it (i.e. leaving out the CreatedBy), use that for what it is, the model for the view and don't use the domain entity model for that.
Something like this
public ActionResult Edit(RequestViewModel model)
{
if (ModelState.IsValid)
{
var currentUser = User.Identity.Name;
var emp = db.Employees.SingleOrDefault(e => e.ApplicationUser == db.Users.FirstOrDefault(u => u.UserName.Equals(currentUser)));
if (emp != null)
{
var domainModel = GetRequestById(model.Id) //Or whatever method you have for this
//Update whatever you want in domainModel, basically all apart from CreatedBy from the model
domainModel.Property1 = model.Property1
//Save the updated domainModel
}
// some code
}
}
Being your RequestViewModel something like this
public class RequestViewModel
{
Guid Id {get; set;} //Or whatever Id you use (int...)
//Rest of the properties you really want them to be able to edit
}
This way you could even add the CreatedBy to the viewmodel if you for example wanted to display it in your edit view but you won't update it in the post back if you wanted (and therefore use it as viewmodel for the create action as well, although many times I don't mind and actually prefer to create a edit viwmodel and a create viewmodel if they differ)
Firstly, new to Asp.Net MVC and ADO.NET entity data model.
While practicing an example, I created a table but forgot to set "Identity specification" to true for PK.
Created a model for MVC Application using entity data Model and worked fine.
Later on I've have set that "Identity specification" to true for that table in the DB.
When I try to insert a record an exception is raised and record does not get inserted.
{"Cannot insert explicit value for
identity column in table 'Contacts'
when IDENTITY_INSERT is set to OFF."}
Here is the digner created class in the model, which should have changed as per schema changes in DB
public static Contact CreateContact(int id, string firstName, string lastName, string phone, string email)
{
Contact contact = new Contact();
contact.Id = id;
//
return contact;
}
There is no need for "Id" variable in the above Method Signature as but it is still auto generating that.
How can we make our model to refresh itself or manually,
if the database schema is updated.
NOTE: using C#, ASP.NET MVC
Thanks
Configure your DB schema correctly, then right-click your model and choose "Update Model from Database." This will correct the SSDL in your EDMX, which tells the EF that the id is store generated.
It will not, however, remove the id argument from the CreateContact method. The EF's code generator puts all non-nullable properties in the signature to this method. But once you have updated the SSDL, you should no longer get the exception when you save; if you want to use this method (you don't have to), you can just pass a 0.