Update an existing entry with Entity Framework - c#

I want to update an existing entry with entity framework and here is my current code:
[HttpPost]
public IActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
var result = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
if (result == null)
return RedirectToAction("Index", "Products");
_productRepository.Update(product);
//result = product;
_productRepository.Save();
return View("Edit", result);
}
What have I tried:
result = product; doesn't seem to update the row in db.
public void Update(T item)
{
_context.Entry(item).CurrentValues.SetValues(item);
}
doesn't seem to update the row in db.
result.Title = product.Title - works, but then I have to do this for each field, is there a way to update a row simply by replacing the values with another object?

Edit
Actually, I realized that the code below will not work because you are already tracking the same entity, this is caused by this line:
var result = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
So you need to either remove that line and just use the Update method below with the product object as the parameter or use the result object and update it based on the data from your product class and then save:
result.Name = product.Name;
[...]
In this case you don't need to call _repository.update, just _repository.save
Using product to update
Assuming your Product class is an object of the same class as your Product entity class you need to make sure that it is being tracked by entity framework and to mark it as modified before it can be saved:
To do that, modify your update method as follows:
public void Update(T item)
{
if (!_context.Set<T>().Local.Any(e => e == item))
{
_context.Set<T>().Attach(item);
}
_context.Entry(item).State = EntityState.Modified
}
Then just save it and it should work:
_productRepository.Update(product);
_productRepository.Save();
A better approach?
Instead of sending entity framework entities back and forth to and from your views, you could create a model class specifically for your view and then retrieve and update your database entity models as needed:
For example if your Product database entity looks like this:
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public int InternalId { get; set; }
}
In your view you don't need / want to use the InternalId field, so you would have a model in your Website assembly that could look like the following:
public class ProductModel
{
public int Id { get; set; }
public string ProductName { get; set; }
}
And then in your controller, this is what you will use:
[HttpPost]
public IActionResult Edit(ProductModel product)
{
if (!ModelState.IsValid)
{
return View(product);
}
var dbProduct = _productRepository.Query().FirstOrDefault(x => x.Id == product.Id);
if (dbProduct == null)
{
//Product doesn't exist, create one, show an error page etc...
//In this case we go back to index
return RedirectToAction("Index", "Products");
}
//Now update the dbProduct using the data from your model
dbProduct.ProductName = product.ProductName;
If you have a lot of fields, you don't want to do this manually, there are some libraries that will do this for you, for example, AutoMapper or my personal favorite (faster, easier to use) ValueInjecter
Using ValueInjecter you would do something like this to assign all the common properties automatically
dbProduct.InjectFrom(product);
Finally, just call save, this time you don't need to change the state because EF is already tracking your entity:
_productRepository.Save();

Related

EF Core 5 One-to-many relationship problem

In this example a user has zero or many bills, one bill can be assigned to one user. Bill can also be created but never assigned.
public class User
{
public int Id{ get; set; }
public List<Bill> bills{ get; set; }
}
public class Bill
{
public int Id { get; set; }
public int userId{ get; set; }
public User user{ get; set; }
}
I've also added this in my DB context configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Bill>()
.HasOne(b => b.user)
.WithMany(u => u.bills)
.HasForeignKey(b => b.userId);
}
I've realized it through a unit of work + repository pattern. In my BillService.cs I would like to have a method that allows me to update/add a bill and assign it to a user.
If the user doesn't exist in DB it should add it. If the user exists it should update it.
I've tried two approaches.
First:
public async Task<void> AddUpdateBill(AddBillModel model){
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
Second:
public async Task<void> AddUpdateBill(AddBillModel model)
{
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
In both cases, I've got the problem of duplicated primary-key or entity already tracked.
Which is the best approach or the right way to do it?
EDIT: Sorry, BillRepo and BillRepository are the same class.
public async Task<Bill> GetByID(int id)
{
return await context
.bill
.Include(b => b.user)
.Where(b=> b.id == id)
.FirstOrDefaultAsync();
}
public void Update(Bill bill)
{
context.Entry(bill).CurrentValues.SetValues(bill);
}
The first approach seems more right (to me).
First of all, comply with the naming rules: all properties must begin with upper case characters. "Bills", "UserId", "User" in your case.
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
You don't need it here
bill.user = model.user;
because you have just attached your entity to context and updated/inserted it.
Also, don't forget to format your code, for example https://learn.microsoft.com/ru-ru/dotnet/csharp/programming-guide/inside-a-program/coding-conventions
It would be useful to consider inserting/updating your entities not straight from the model, something like:
if( unitOfWork.UserRepo.GetById(model.userId) == null){
var user = new User
{
//set properties
};
unitOfWork.UserRepo.Insert(user);
unitOfWork.Save();
bill.userId = user.Id;
}
Here:
if( unitOfWork.UserRepo.GetById(model.userId) == null){...
you retrieve the User from UserRepo but don't assign it to any variable. This may cause the exception stating that there are multiple tracked entities with the same ID.
Try to retrieve (including bills) or create the User entity and add the new bill in there. Then insert User entity to DB (if it was not there) and simply Save your work.

How can I tell why/how some LINQ queries work with each other and others don't?

Take the following EF Class:
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<Property> Property { get; set; }
}
public class Property
{
public int ID { get; set; }
public string Name { get; set; }
public bool Lock { get; set; }
public Person Person { get; set; }
public int PersonID
}
I can pretty much make everything work as expected - including a delete action for Person that also deletes all their property. However, as my code gets more complicated, I want to make the logic slightly more advanced.
In the above example, we have something elsewhere that will set the bool lock for property. In this case, I want to disable delete on person when any property for that person has a lock of true.
The default Delete controller code has:
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var person = await _context.People
.FirstOrDefaultAsync(m => m.ID == id);
if (person== null)
{
return NotFound();
}
return View(person);
}
And the Delete confirm has:
public async Task<IActionResult> DeleteConfirmed(int id)
{
var person= await _context.people.FindAsync(id);
_context.people.Remove(person);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
I know the code to do what I want is:
foreach (var item in person.Property)
{
if item.locked==true
return("error")
}
Now the fun stars! - The old EF4 virtual keyword on properties I'm used to doesn't work - so, I can't iterate over the property because it is currently null. in most instances, I have to call .include()
On the first delete, this modifies:
var person = await _context.People
.FirstOrDefaultAsync(m => m.ID == id);
to
var person = await _context.People.Include(x=>x.property)
.FirstOrDefaultAsync(m => m.ID == id);
which seems to work fine.
However, the second one:
var person = await _context.people.FindAsync(id);
doesn't seem to work. The moment I put the .Include in, it states error CS1061 that there is no definition for FindAsync.
In all honesty, I am not too sure what the need is for two different ways of looking at an ID in the first place... I can only assume that when looking for an ID in the first delete that may not exist, firstordefault is the best and when confirming a delete, find is best.... however, this is what the scaffolding does and I don't feel I know enough to question this.
I however want to be a better developer and would love to understand what is wrong with the code and for future, how do I know what can be combined and what can't as I don't feel I am learning here, I am just randomly trying different things until I find one combination that works.
A few things:
I'd consider checking whether the person is Locked before enabling a Delete button, or immediately on clicking the delete button rather than on confirming a delete.
With this code:
var person = await _context.People
.FirstOrDefaultAsync(m => m.ID == id);
if (person== null)
return NotFound();
return View(person);
Entities should represent data state, not view state. Returning entities to the view will lead to problems. If lazy loading is supported/enabled this can trigger performance issues when lazy-loads get triggered by serialization, it can also lead to errors due to cyclical references. It exposes more information about your data structure, and data in general that the client does not need (more data over the wire and more information for hackers). Instead, leverage a ViewModel POCO class containing just the data your view needs and use .Select() to populate it.
Next, avoid the crutch of FirstOrDefault. This can lead to unintended bugs remaining hidden. If there is 1 entity expected, use Single or SingleOrDefault. Your application should handle exceptions gracefully and impartially. If someone sends an invalid ID, fail and terminate the session. Basically do not trust the client not to tamper.
var person = await _context.People
.Select(x => new PersonViewModel
{
PersonId = x.ID,
Name = x.FirstName + " " + x.LastName,
// etc.
}).SingleAsync(x => x.ID == id);
return View(person);
When checking data state on the confirm, you receive and ID and want to confirm before issuing the delete, you can query the required detail, but then for a delete you don't need the entire entity provided you trust the ID. Something like this isn't needed:
foreach (var item in person.Property)
{
if item.locked==true
return("error")
}
Instead:
var isLocked = context.People.Where(x => x.ID == id)
.Select(x => x.Property.Any(p => p.isLocked))
.Single();
This will throw if the person ID isn't found, and return a Bool True of False if any of the Property entries for that person are locked.
From there you can use a simple "trick" to delete an entity without first loading it:
if (!isLocked)
{
var person = new Person { ID = id };
context.People.Attach(person);
context.People.Remove(person);
context.SaveChanges();
}
Alternatively if you want to load the Person to have access to other properties, such as to create an audit record or may want to display info anyways as part of the error message, then you can substitute the above examples with:
var personData = context.People.Where(x => x.ID == id)
.Select(x => new
{
Person = x,
IsLocked = x.Property.Any(p => p.isLocked))
}).Single();
if (!personData.isLocked)
{
context.People.Remove(personData.Person);
context.SaveChanges();
}

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

ASP.NET MVC 3 Database Context Querying

I have my Database Context:
public class ProductContext : DbContext
{
public ProductContext() : base ("DefaultConnection") {}
public DbSet<Product> Products {get;set;}
}
and my Repository:
public class ProductRepository : IProductRepository
{
private ProductContext _dbContext = new ProductContext();
public IQueryable<Product> Products { get { return _dbContext.Products; } }
}
when I query my database in the Edit Action:
public ActionResult Edit(Guid id)
{
var item = _repository.Products.FirstOrDefault(x => x.Id.Equals(id));
return View(item);
}
I would usually use a ViewModel but this is purely to show the scenario.
When I query the database using the var item line, does EntityFramework change the state of that item.
Can I pass around that item through a multitude of Services in the Service Layer and then finally save it using my method:
public void SaveEntity<TEntity>(TEntity entityToSave) where TEntity : DbEntity
{
if (entityToSave.Id.Equals(Guid.Empty))
_dbContext.Set<TEntity>().Add(entityToSave);
else
_dbContext.Entry<TEntity>(entityToSave).State = EntityState.Modified;
_dbContext.SaveChanges();
}
It won't throw an exception saying that there is already a Entity with the same Id as the one you're trying to Save?
So after trial and error, it seems that this works perfectly fine, and it doesn't bring back any errors. There is one thing to look out for:
This navigation property:
public virtual Category Category { get;set; }
public Guid CategoryId { get;set; }
That could reside in the Product model has a little gotcha, that is:
When editing or saving a new Product, you should only set the CategoryId and not just the Category exclusively because you will get duplicate Category entries every time you edit or save if you use the a Category that already exist within the database...
I think you should the navigation property solely for your ease, not for use when modifying entities...

Exclude Property on Update in Entity Framework

I've been looking for a proper way to mark a property to NOT be changed when updating a model in MVC.
For example, let's take this small model:
class Model
{
[Key]
public Guid Id {get; set;}
public Guid Token {get; set;}
//... lots of properties here ...
}
then the edit method MVC creates looks like this:
[HttpPost]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
now if my View does not contain the Token, it will be nullified through that edit.
I'm looking for something like this:
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();
The best way so far I found is to be inclusive and set all properties I want included by hand, but I really only want to say which ones to be excluded.
we can use like this
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
it will update but without Token property
Anyone looking for how to achieve this on EF Core. It's basically the same but your IsModified needs to be after you add the model to be updated.
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
#svendk updated:
And if you (as me) are wondering why model don't have the token either before or after db.SaveChanges(), it's because with Update, the entity is actually not retrieved - only an SQL Update clause is sent - so the context don't know of your model's preexisting data, only the information you gave it in db.Update(mode). Even if you Find(model.id) you are not getting your context updated, as there is already loaded a model in the context, it is still not retrieved from database.
If you (as me) wanted to return the finished model as it looks like in the database, you can do something like this:
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
// New: Reload AFTER savechanges, otherwise you'll forgot the updated values
db.Entry(model).Reload();
Now model is loaded from database with all the values, the updated and the (other) preexisting ones.
Create new model that will have limited set of properties that you want to update.
I.e. if your entity model is:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public bool Enabled {get;set;}
}
You can create custom view model that will allow user to change Name, but not Enabled flag:
public class UserProfileModel
{
public int Id {get;set;}
public string Name {get;set;}
}
When you want to do database update, you do the following:
YourUpdateMethod(UserProfileModel model)
{
using(YourContext ctx = new YourContext())
{
User user = new User { Id = model.Id } ; /// stub model, only has Id
ctx.Users.Attach(user); /// track your stub model
ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
ctx.SaveChanges();
}
}
When you call this method, you will update the Name, but Enabled property will remain unchanged. I used simple models, but I think you'll get the picture how to use it.
I made an easy way to edit properties of entities I will share with you.
this code will edit Name and Family properties of entity:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
And this code will ignore to edit Name and Family properties of entity and it will edit another properties:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
Use this extension:
public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
where TEntity : class
{
var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
if (find == null)
throw new Exception("id not found in database");
if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (!properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
context.SaveChanges();
}
public enum TypeOfEditEntityProperty
{
Ignore,
Take
}
I guess you don't want the property to be changed just in some cases, because if you are not going to use it never in your application, just remove it from your model.
In case you want to use it just in some scenarios and avoid its "nullification" in the case above, you can try to:
Hide the parameter in the view with HiddenFor:
#Html.HiddenFor(m => m.Token)
This will make your original value to be kept unmodified and passed back to the controller.
Use TryUpdateModel: http://msdn.microsoft.com/en-us/library/dd460189(v=vs.108).aspx
Load again your object in the controller from your DBSet and run this method. You can specify both a white list and a blacklist of parameters that shall or shall not be update.
I use dapper but my solution will work for EF too. If you are potentially going to change your ORM in the future my solution might be better for you.
class Model
{
public Foo { get; set; }
public Boo { get; set; }
public Bar { get; set; }
// More properties...
public void SafeUpdate(Model updateModel, bool updateBoo = false)
{
// Notice Foo is excluded
// An optional update
if (updateBoo)
Boo = updateModel.Boo;
// A property that is always allowed to be updated
Bar = updateModel.Bar;
// More property mappings...
}
}
As you can observe I allow updates for only the properties that I wish.
A downside of my approach is that you'll need to manually update this method if you introduce new properties (that are allowed to be updated) to your model. But I believe this in not always a downside but sometimes an advantage, in the sense that you'll need to be aware of what is being updated, this might be beneficial in terms of security.
Let us see a demonstration of this approach.
// Some code, DI etc...
public IActionResult Put([FromBody] Model updateModel)
{
var safeModel = new Model();
safeModel.Update(updateModel);
// Add validation logic for safeModel here...
_modelRepository.Update(safeModel);
}

Categories