I am working in webapi. I have one update method to perform modification. There are number of properties but i only wants to update few fields.
So i just skipped the unwanted fields using entry.Property(propertyName).IsModified = false; in data layer. All my logic working fine but after update, when i got the new update entry, it didn't have the fields which are not updated
My Controller Code:
[Route("{id:int}")]
public async Task<IHttpActionResult> Put(int id, MyModel model)
{
model.Id = id;
bool result = await _modelLogic.UpdateData(item);
if (!result)
{
return BadRequest("Could not Save to the database");
}
return await GetModel(item.Id);
}
[Route("{id:int}", Name = "GetModelById")]
public async Task<IHttpActionResult> GetModel(int id)
{
MyModel model = await _modelLogic.GetModelAsync(id);
if (Model == null)
{
return NotFound();
}
return Ok(model);
}
My Business Logic:
public async Task<bool> UpdateData(MyModel model)
{
model.RecordStatus = DataStatus.Active;
string[] excludedProperties = new[] {"RegistrationId", "StartDate", "ProtocolType", "Code" };
_repo.Update(model, excludedProperties);
bool status = await _repo.SaveAsync();
return status;
}
Here RegistrationId is a foreign key.
My Data Code:
public void Update(MyModel model, string[] excludedProperties)
{
excludedPropertiesInUpdate = excludedPropertiesInUpdate.Union(excludedProperties).ToArray();
base.Update(model);
}
My Generic Repository / base repository
internal string[] excludedPropertiesInUpdate = new[] { "CreatedDate", "CreatedBy" };
public void Update(T entity)
{
entity.UpdatedDate = DateTime.UtcNow;
var entry = _context.Entry(entity);
entry.State = EntityState.Modified;
//Restrict Modification for Specified Properties
foreach(string propertyName in excludedPropertiesInUpdate)
{
entry.Property(propertyName).IsModified = false;
}
}
Like i told, all logic working fine. But when it shows the response, the fields which are not updated shown as null. eg: RegistrationId, Code etc. But in database its there not updated anything
You only store new values in database, but do not load unmodified properties to entity.
internal string[] excludedPropertiesInUpdate = new[] { "CreatedDate", "CreatedBy" };
public void Update(T entity)
{
entity.UpdatedDate = DateTime.UtcNow;
var entry = _context.Entry(entity);
entry.State = EntityState.Modified;
//Restrict Modification for Specified Properties
foreach(string propertyName in excludedPropertiesInUpdate)
{
entry.Property(propertyName).IsModified = false;
}
_context.SaveChanges();
//load actual values from the database.
entry.Reload();
}
Related
I am building a simple MVC CRUD without using a database, but just making methods in a Repository model class.
To make it easier to understand i have 2 model classes. MyNote in which i have some properties and NoteRepository in which i have a list with the properties.
Then I've made a NoteController and i have already made Get and Create methods, but i can't seem to figure out what to write to make an Edit and Delete method? Hope you guys can help.
Here you will see some of the code from my project:
[HttpPost]
public ActionResult Create(MyNote mn)
{
try
{
note.Create(mn);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
this is the create from the Controller.
public static List<MyNote> notes = new List<MyNote>();
public NoteRepository()
{
notes.Add(new MyNote() { ID = 1, Titel = "Morgenmad", OprettelsesDato = DateTime.Now, Note = "Spis morgenmad i dag" });
notes.Add(new MyNote() { ID = 2, Titel = "Frokost", OprettelsesDato = DateTime.Now, Note = "Spis frokost i dag" });
notes.Add(new MyNote() { ID = 3, Titel = "Aftensmad", OprettelsesDato = DateTime.Now, Note = "Spis aftensmad i dag" });
}
public void Create(MyNote mn)
{
notes.Add(mn);
}
here is the repository class with the list and the method for the create method.
and please, ask if i have missed something! Thank you :-)
It looks like you're using a List for your in-memory repository. For delete, you can implement something like this:
public bool Delete (MyNote noteToDelete) {
return notes.Remove(noteToDelete);
}
Edit: However, in this case, the list will check for reference equality. Since you have an ID, which I will assume is unique, you can instead do this:
public bool Delete(MyNote noteToDelete) {
var matchingNote = notes.FirstOrDefault(n => n.ID == noteToDelete.ID);
return notes.Remove(matchingNote);
}
You could also implement IEquatable on your MyNote class to change how your notes are compared with each other, and return a valid match when the IDs are the same.
For the IEquatable example, you would want to change the class definition for MyNote to look like:
public class MyNote : IEquatable<MyNote>
and add in the following code to the MyNote class:
public override bool Equals(object obj) {
if (obj == null) return false;
Part objAsNote = obj as MyNote;
if (objAsNote == null) return false;
else return Equals(objAsNote);
}
public bool Equals(MyNote otherNote) {
if(otherNote == null) return false;
return (this.ID.Equals(otherNote.ID));
}
public override int GetHashCode(){
return this.ID;
}
You can do something like this:
public ActionResult Edit(MyNote noteToEdit)
{
var oldNote = notes.FirstOrDefault(n => n.Id == noteToEdit.Id);
if(oldNote == null)
return View(); //With some error message;
oldNote.Title = noteToEdit.Title;
oldNote.OprettelsesDato = DateTime.Now;
oldNote.Note = noteToEdit.Note;
return RedirectToAction("Index", "Note");
}
public ActionResult Delete(int id)
{
var noteToRemove = notes.FirstOrDefault(x => x.Id == id);
if(noteToRemove == null)
return View(); //With some error message;
notes.Remove(noteToRemove);
return RedirectToAction("Index", "Note");
}
When you are editing your note, i recommend you to use AutoMapper to make your code more easy to maintain.
I am developing an MVC 5 application using EF 6. The auto-generated POST method for Edit is:
public ActionResult Edit([Bind(Include = "Id,Name,Address,Phone,SSNo,D1")] ABC abc)
{
if (ModelState.IsValid)
{
db.Entry(abc).State = EntityState.Modified;
db.SaveChanges();
}
return View(abc);
}
Is there any procedure/method by which I can get that which entries are modified and what were the original values of those entries. I have tried the method marked as an answer in this question but it didn't get any changes although changes have been made, i.e. the loop does not iterates.
My code is:
public ActionResult Edit([Bind(Include = "Id,Name,Address,Phone,SSNo,D1")] ABC abc)
{
if (ModelState.IsValid)
{
db.ABCs.Attach(abc);
var myObjectState = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager.GetObjectStateEntry(abc);
var modifiedProperties = myObjectState.GetModifiedProperties();
foreach (var propName in modifiedProperties)
{
Console.WriteLine("Property {0} changed from {1} to {2}",
propName,
myObjectState.OriginalValues[propName],
myObjectState.CurrentValues[propName]);
}
Console.ReadKey();
db.Entry(abc).State = EntityState.Modified;
db.SaveChanges();
return Json(abc);
}
return View(abc);
}
ABC model contains many values other than mentioned in method parameters, but I am only passing those which can be edited
What I am trying to do is to maintain a log in my database of all the changes made to a document. I want to include only the changes and the original values before modification in the log not the complete record.
How can I get this?
I implemented this extension method for you:
namespace YourProject.Extensions
{
public static class ModelExtensions
{
public static IEnumerable<KeyValuePair<string, object>> ModifiedValues<T>(this T obj, T modifiedObject)
{
foreach (var property in typeof(T).GetProperties().Where(p => !p.GetGetMethod().IsVirtual))
{
if (property.GetValue(obj).ToString() != property.GetValue(modifiedObject).ToString())
{
yield return new KeyValuePair<string, object>(property.Name, property.GetValue(modifiedObject));
}
}
}
}
}
It's neither the fastest nor the most efficient, but worked in my tests.
Use:
var originalEntity = await db.Entities.AsNoTracking().FirstOrDefaultAsync(e => e.EntityId == modifiedEntity.EntityId);
var modifiedValues = originalEntity.ModifiedValues<MyEntity>(modifiedEntity).ToList();
I need to map two entities with one to zero or one relationship. when I create a principal object it works.
I have the following models:
//Principal class
public class Sugerencia
{
public int ID { get; set; }
[DisplayName("TÃtulo")]
public string Titulo { get; set; }
//More properties simplified for example
}
//Related class
public class Resultado
{
[ScaffoldColumn(false)]
public int SugerenciaID { get; set; }
[ScaffoldColumn(false)]
public int ID { get; set;}
//More properties
}
This is my DbContext Class
public class SugerenciaContext : DbContext
{
public SugerenciaContext() : base("SugerenciaContext")
{
}
public DbSet<Sugerencia> Sugerencias { get; set; }
public DbSet<Resultado> Resultados { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Resultado>()
.HasRequired(r => r.Sugerencia)
.WithOptional(s => s.Resultado);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Note the OnModelCreate overriding for map the relationship.
This is the ResultadoController Create actions (POST and GET)
//GET
public ActionResult Create(int? id)
{
ViewBag.ID = new SelectList(db.Sugerencias, "ID", "Titulo");
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
//I am retrieving the principal model instance to set resultado.Sugerencia property.
Sugerencia sugerencia = db.Sugerencias.Find(id);
if (sugerencia == null)
{
return HttpNotFound();
}
Resultado resultado = new Resultado();
resultado.Sugerencia = sugerencia;
return View(resultado);
}
//POST
public ActionResult Create([Bind(Include = "ID,SugerenciaID,Responsable,Resultados,Fecha")] Resultado resultado, string status)
{
if (ModelState.IsValid)
{
resultado.Fecha = System.DateTime.Now;
Sugerencia sugerencia = db.Sugerencias.Find(resultado.ID);
if (sugerencia == null)
{
return HttpNotFound();
}
sugerencia.Status = status;
//Here I am modifying the state property of Sugerencia model.
db.Entry(sugerencia).State = EntityState.Modified;
db.Entry(resultado).State = resultado.ID == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ID = new SelectList(db.Sugerencias, "ID", "Titulo", resultado.ID);
return View(resultado);
}
The error I am getting is triggered in db.SaveChenges();line. When I am trying to add a new Resultado model. However when I invoke the Create action for a principal instance which already has a related object in the DB it recreates the object rewriting the values on DB as expected.
In the create view of Resultado controller I have to update one property of Sugerencia model.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded
Note I am inserting or modifying the instance. I am debugging via Command Window and setting a breakpoint at that line. When printing resultado and sugerencia variables all properties seem to be valid.
Thanks for considering my question.
Looking at your code, this is what I think is happening:
// This is not needed, by the way, since the previous line should have done this
db.Entry(sugerencia).State = EntityState.Modified;
// This is probably setting resultado's state to Modified, even for new items!
db.Entry(resultado).State = resultado.ID == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
First of all, when would you need to set the resultado state to EntityState.Modified in a Create action? Also, I'm guessing that your model configuration is setting Resultado.ID as the foreign key, so when you previously set resultado.Sugerencia = sugerencia, Resultado.ID was set to a value not equal to zero.
You should be fine with
db.Entry(resultado).State = EntityState.Added;
I am trying to override Entity Framework's SaveChanges() method to save auditing information. I begin with the following:
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;
List<ObjectStateEntry> objectStateEntryList = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Modified
| EntityState.Deleted).ToList();
foreach (ObjectStateEntry entry in objectStateEntryList)
{
if (!entry.IsRelationship)
{
//Code that checks and records which entity (table) is being worked with
...
foreach (string propertyName in entry.GetModifiedProperties())
{
DbDataRecord original = entry.OriginalValues;
string oldValue = original.GetValue(original.GetOrdinal(propertyName)).ToString();
CurrentValueRecord current = entry.CurrentValues;
string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString();
if (oldValue != newValue)
{
AuditEntityField field = new AuditEntityField
{
FieldName = propertyName,
OldValue = oldValue,
NewValue = newValue,
Timestamp = auditEntity.Timestamp
};
auditEntity.AuditEntityField.Add(field);
}
}
}
}
}
Problem I'm having is that the values I get in entry.OriginalValues and in entry.CurrentValues is always the new updated value:
The problem has been found:
public ActionResult Edit(Branch branch)
{
if (ModelState.IsValid)
{
db.Entry(branch).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(branch);
}
Saving an update in the above way seems to cause the ChangeTracker to not pick up the old values. I fixed this by simply making use of ViewModels for all the required updates:
public ActionResult Edit(BranchViewModel model)
{
if (ModelState.IsValid)
{
Branch branch = db.Branch.Find(model.BranchId);
if (branch.BranchName != model.BranchName)
{
branch.BranchName = model.BranchName;
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(model);
}
To get a copy of the old values from the database without updating the EF entity you can use
context.Entry(yourEntity).GetDatabaseValues().ToObject()
or
yourEntity.GetDatabaseValues().ToObject()
Caution: this does instigate a database call.
The easiest way to do for me was:
var cadastralAreaDb = await _context.CadastralAreas.FindAsync(id).ConfigureAwait(false);
var audit = new Audit
{
CreatedBy = "Edi"
};
_context.Entry(cadastralAreaDb).CurrentValues.SetValues(cadastralArea);
await _context.SaveChangesAsync(audit).ConfigureAwait(false);
This is tested in ASP NET Core 3.1
here's my question: I have an MVC3 C# application and I need to update a value in a DbSet from the Edit controller, so the T item's value will be replaced with a new one. I don't want to delete the item and add it again. I cannot figure out how to do this. DbSet doesn't seem to have something like an index.
Here's my Edit controller:
public class ItemController : Controller
{
private SESOContext db = new SESOContext();
private FindQrs fqr = new FindQrs();
[HttpPost]
public ActionResult Edit(Item item)
{
if (ModelState.IsValid)
{
db.Entry(item).State = EntityState.Modified;
Qr qr = fqr.FindQr(item.QR);
// update somewhere here
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.SetID = new SelectList(db.Sets, "SetID", "Name", item.SetID);
return View(item);
}
}
EDIT: The above controller is for the Item entity which on Create action creates instances of the Qr entity. I just want to add something which on Edit action will update the Value field of the Qr entity with the value from the view (the Value field of Qr is supposed to be unique).
Where FindQrs method looks like this:
public class FindQrs
{
private SESOContext db = new SESOContext();
public Qr FindQr(string qr)
{
List<Qr> qrList = db.Qrs.ToList<Qr>();
Qr foundQr = qrList.Find(delegate(Qr qrDel) { return qrDel.Value == qr; });
return foundQr;
}
}
And Qr is a class which contains only an ID and a string Value fields.
Qrs is the mentioned DbSet from the context. It looks like this:
public DbSet<Qr> Qrs { get; set; }
Any help will be appreciated.
You are perhaps updating the value in a different DbContext. Try this:
[HttpPost]
public ActionResult Edit(Item item)
{
if (ModelState.IsValid)
{
db.Entry(item).State = EntityState.Modified;
Qr qr = fqr.FindQr(item.QR, db);
// update somewhere here
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.SetID = new SelectList(db.Sets, "SetID", "Name", item.SetID);
return View(item);
}
public class FindQrs
{
public Qr FindQr(string qr, SESOContext db) // pass in the db context
{
List<Qr> qrList = db.Qrs.ToList<Qr>();
Qr foundQr = qrList.Find(delegate(Qr qrDel) { return qrDel.Value == qr; });
return foundQr;
}
}