I have the following model in my Asp.Net Mvc project.
When a new object is created, a new list of both Options and Foto's is also created so that you can simply add new Options and Pictures to the object.
public class VehicleModels
{
[Key]
public virtual int Id { get; set; }
public virtual string Naam { get; set; }
public virtual string Merk { get; set; }
public virtual string Brandstof { get; set; }
public virtual string Kleur { get; set; }
public virtual string TypeVanMerk { get; set; }
public virtual string TypeVanTransmissie { get; set; }
public virtual int Kilometerstand { get; set; }
public virtual int Bouwjaar { get; set; }
public virtual int AantalDeuren { get; set; }
public List<Optie> Options { get; set; }
public List<Foto> Fotos { get; set; }
public VehicleModels()
{
Options = new List<Optie>();
Fotos = new List<Foto>();
}
}
I have this Edit in my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, HttpPostedFileBase upload)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var VehicleToUpdate = db.VehicleModels.Find(id);
if (TryUpdateModel(VehicleToUpdate))
{
try
{
if (upload != null && upload.ContentLength > 0)
{
if (VehicleToUpdate.Fotos.Any(f => f.VehicleModelsID == VehicleToUpdate.Id))
{
db.Fotos.Remove(VehicleToUpdate.Fotos.First(f => f.VehicleModelsID == VehicleToUpdate.Id));
}
var picture = new Foto
{
FotoNaam = System.IO.Path.GetFileName(upload.FileName),
ContentType = upload.ContentType
};
using (var reader = new System.IO.BinaryReader(upload.InputStream))
{
picture.Content = reader.ReadBytes(upload.ContentLength);
}
VehicleToUpdate.Fotos.Add(picture);
}
db.Entry(VehicleToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
return View(VehicleToUpdate);
}
My Problem:
While debugging i found that this line of code (var VehicleToUpdate = db.VehicleModels.Find(id);) inside my Edit method creates a new object of the model and seems to bind the properties to that new object.
But because it passes the constructor, both lists are wiped clean.
The problem shows itself when i try to change the picture of the object, because the list is empty, it does not delete any and it also does not add the new image, instead it just adds the old image again, leaving me with 2x the same image.
Should i instantiate my Lists somewhere else?
If so, where would be the appropriate place?
Side note: I followed this tutorial to add images to my project.
Both lists should not be wiped clean by var VehicleToUpdate = db.VehicleModels.Find(id);. They should be populated appropriately.
If they are not getting populated and are coming as empty then check your database. Either the data is not there. If the data is there then the foreign key constraints are broken. Check and correct the same. After correcting database do update the model in edmx file for Entity Framework in your project. After that, the Entity Framework should populate the data appropriately and the image will update instead of adding a duplicate.
Related
I am newer to using EF (used Dapper mostly before) and am having issues in an API app that I have taken over mostly built.
I have a method (previous developer built) that is creating or updating a userTerminal. It had a FK to Terminal the current record I am adding to userTerminal is tied to a terminal that already exists.
This is the method
public void AddTerminalUser(UserTerminal userTerminal)
{
using (var context = new GateManagementEntities(connectionString))
{
//Modified existing
if (context.UserTerminals.Count(z => z.TerminalCode == userTerminal.TerminalCode && z.UserId == userTerminal.UserId) > 0)
{
UserTerminal ut = new UserTerminal() { Id = userTerminal.Id, IsDeleted = true, LastUpdated = DateTime.Now };
var entry = context.Entry(ut);
entry.Property(z => z.LastUpdated).IsModified = true;
entry.Property(z => z.IsDeleted).IsModified = true;
}
else
{
//New Entity
userTerminal.CreatedDate = DateTime.Now;
userTerminal.LastUpdated = DateTime.Now;
context.UserTerminals.Add(userTerminal);
}
context.SaveChanges();
}
}
and this is the class being passed in
public partial class UserTerminal
{
public int Id { get; set; }
public string UserId { get; set; }
public Nullable<int> TerminalCode { get; set; }
public Nullable<bool> IsDeleted { get; set; }
public Nullable<System.DateTime> LastUpdated { get; set; }
public Nullable<System.DateTime> CreatedDate { get; set; }
public string UpdatedBy { get; set; }
public virtual Terminal Terminal { get; set; }
public virtual UserPreference UserPreference { get; set; }
}
public partial class Terminal
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Terminal()
{
this.GateAssignments = new HashSet<GateAssignment>();
this.GateAssignments1 = new HashSet<GateAssignment>();
this.GateDefinitions = new HashSet<GateDefinition>();
this.UserTerminals = new HashSet<UserTerminal>();
}
public int TerminalCode { get; set; }
public string City { get; set; }
public string State { get; set; }
public Nullable<System.DateTime> LastUpdated { get; set; }
public Nullable<System.DateTime> CreatedDate { get; set; }
public string UpdatedBy { get; set; }
public Nullable<int> GateTypeId { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GateAssignment> GateAssignments { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GateAssignment> GateAssignments1 { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GateDefinition> GateDefinitions { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<UserTerminal> UserTerminals { get; set; }
public virtual GateType GateType { get; set; }
}
TerminalCode is what links Terminal to TerminalUser
I checked the UserTerminal object passed in and the UserTerminal's TerminalCode and it is populated with the terminal code I expect (I know that terminal code is in the terminal table) and the Terminal object is fully populated as well with the TerminalCode I expect. It is trying to insert the already existing terminal record into the database causing a PK duplication error.
How do I get it to only all terminal if it doesn't already exist? It seems to follow the same pattern that other methods that are similar are doing, so I haven't been able to figure out why this is different.
Passing entities between context instances is troublesome. The issue when your context doesn't know about UserTerminal while that new UserTerminal references a real, existing Terminal record is that the new Context instance doesn't know about that Terminal either.
If you load the Terminal (and any other related entities) into the context prior to adding the UserTerminal, it should work, however to be safe it's generally better to associate the new UserTerminal with references to the terminal that the context knows.
For example:
public void AddTerminalUser(UserTerminal userTerminal)
{
using (var context = new GateManagementEntities(connectionString))
{
//Modified existing
if (context.UserTerminals.Count(z => z.TerminalCode == userTerminal.TerminalCode && z.UserId == userTerminal.UserId) > 0)
{
UserTerminal ut = new UserTerminal() { Id = userTerminal.Id, IsDeleted = true, LastUpdated = DateTime.Now };
var entry = context.Entry(ut);
entry.Property(z => z.LastUpdated).IsModified = true;
entry.Property(z => z.IsDeleted).IsModified = true;
}
else
{
//New Entity
userTerminal.CreatedDate = DateTime.Now;
userTerminal.LastUpdated = DateTime.Now;
var terminal = context.Terminals.Find(userTerminal.TerminalCode);
userTerminal.Terminal = terminal;
// Repeat above for all other references.
context.UserTerminals.Add(userTerminal);
}
context.SaveChanges();
}
}
I don't advise passing entities around outside of the scope of the DbContext that loaded them. ViewModels / DTOs should serve this purpose and avoid surprises when you expect FK references to resolve automatically. There are cases where contexts will resolve references seemingly sometimes because the related entity is already loaded, but then fail with duplicate PKs in scenarios where the entity hasn't been previously loaded into the context.
I need help with Entity Framework.
Controller:
public ActionResult Create([Bind(Prefix = "visit")] visit visit, [Bind(Prefix = "drugsEdition")] IEnumerable<drugsEdition> drugsEdition, [Bind(Prefix = "accessoryEdition")] IEnumerable<accessoryEdition> accessoryEdition, [Bind(Prefix = "servicesEdition")] IEnumerable<servicesEdition> servicesEdition)
{
Models.VisitDetails visitDetails = new Models.VisitDetails();
visitDetails.visit = visit;
if (ModelState.IsValid)
{
db.visit.Add(visit);
if (drugsEdition != null)
{
foreach (var item in drugsEdition)
{
item.idVisit = visit.id;
db.drugsEdition.Add(item);
}
}
if (accessoryEdition != null)
{
foreach (var item in accessoryEdition)
{
item.idVisit = visit.id;
db.accessoryEdition.Add(item);
}
}
if (servicesEdition != null)
{
foreach (var item in servicesEdition)
{
item.idVisit = visit.id;
db.servicesEdition.Add(item);
}
}
db.SaveChanges();
return RedirectToAction("Details", new { id = visit.id });
}
return View(visitDetails);
}
Model:
[Table("servicesEdition")]
public partial class servicesEdition
{
public int id { get; set; }
public int idService { get; set; }
public int idVisit { get; set; }
public double priceSell { get; set; }
[ForeignKey("idService")]
public virtual services services { get; set; }
}
In this code, I added a new visit to the database, and I want get the visit's id after the code line db.visit.Add (visit). When I add new drugsEdition and new accessoryEdition, this code is correct and added good idVisit, but when I addedservicesEdition idVisit = 0. Why 'servicesEdition' doesn't get good idVisit ?
If you add a virtual navigational property of type Visit, EF will get the corresponding visit id and fill that in when it saves a servicesEdition object on your SaveChanges method call.
I would also renaming the foreign key property to VisitId so that the referential integrity works by convention (naming convention) and you do not need to explicitly use the ForeignKey attribute. I would also recommend you to use PascalCasing when writing C# classes( not necessarily needed for your code to work)
public partial class ServicesEdition
{
public int id { get; set; }
public int idService { get; set; }
public int VisitId { get; set; }
public virtual Visit Visit { get; set; }
public double priceSell { get; set; }
[ForeignKey("idService")]
public virtual services services { get; set; }
}
If you do not want to alter the ServicesEdition entity definition like i described above, another option is to call the SaveChanges() method after you add the Visit entity and then you can access the Id property of that.
db.visit.Add(visit);
db.SaveChanges();
var visitId = db.Id;
//Now you can use visitId to save other entities
The issue is db.visit.Add(visit); does not generate id until hit db.SaveChanges();
You could add db.SaveChanges(); after db.visit.Add(visit);.
Or you will save as a graph like Shyju explained.
I have a controller action which saves data to db on change. Model includes children Files, they are not saved to the database however. Data comes ok from the server, file is added to the model from the request ok, but it just won't save. There is no error occuring.
These are my models:
public class Guest
{
public Guest()
{
this.Files = new List<File>();
}
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string Email { get; set; }
public string Tel { get; set; }
public int CompanyId { get; set; }
public int Votes { get; set; }
public virtual ICollection<File> Files {get; set;}
}
This is my VM
public class GuestViewModel
{
public Guest Guest { get; set; }
public IEnumerable<SelectListItem> Companies { get; set; }
}
and this is the controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit( GuestViewModel guestViewModel, HttpPostedFileBase upload)
{
if (upload != null && upload.ContentLength > 0)
{
var avatar = new File
{
FileName = System.IO.Path.GetFileName(upload.FileName),
FileType = FileType.Picture,
ContentType = upload.ContentType
};
using (var reader = new System.IO.BinaryReader(upload.InputStream))
{
avatar.Content = reader.ReadBytes(upload.ContentLength);
}
guestViewModel.Guest.Files = new List<File> { avatar };
}
if (ModelState.IsValid)
{
Guest guest = new Guest();
guest = guestViewModel.Guest;
_db.Entry(guest).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(guestViewModel);
}
Any help is appreciated.
I think that you have to set the state for the child entities too, because when you attach an entity to a context setting the state directly, any child entities is attacched in "unchanged" state, so is ignored in save changes.
In this case, the state that you need to use for child files is Added.
Hope this helps!
I want to implement this simple scenario ,which I though EF will support out of the box.
I have a parent record named (Skill) and I am adding child records named (LinktoKB) to it. Now after adding a new LinktoKB, I want to return a view containing the up-to-date list of LinkToKBs (inclusing the newly added one).
Now my Post action method to add new LinktoKB is :-
[HttpPost]
[ValidateAntiForgeryToken]
[CheckUserPermissions(Action = "Edit", Model = "Skill")]
public async Task<ActionResult> AddKBLink(AssignKBLinksToSkill assignkblinkToSkill)
{
try
{
if (assignkblinkToSkill.LinkToKB == null)
{
return HttpNotFound();
}
if (ModelState.IsValid)
{
unitofwork.SkillRepository.AddKBLinkToSkill(assignkblinkToSkill, unitofwork.StaffRepository.GetLoginUserName(User.Identity.Name));
await unitofwork.Save();
//i have removed the values from the model state to prevent showing validation error "that the URL and name is required after succfully adding a new link"
// also to show the modified values and not the binded values
string oldlinkURL = assignkblinkToSkill.LinkToKB.URL;
ModelState.Clear();
var skillAfterAddingKBLink = await unitofwork.SkillRepository.FindSkill(assignkblinkToSkill.Skillid, r => r.LinkToKBs);
assignkblinkToSkill.LinktoKBList = skillAfterAddingKBLink.LinkToKBs.ToList(); //get the new lsit from DB after addign the new link
assignkblinkToSkill.LinkToKB.URL = "http://";//reset the values , so that user will not get old vlues
assignkblinkToSkill.LinkToKB.Name = String.Empty;
if (Request.IsAjaxRequest())
{
TempData["Partialmessage"] = string.Format("{0} URL have been Added", oldlinkURL);
return PartialView("AddKBLink", assignkblinkToSkill);
}
TempData["message"] = string.Format("{0} URL have been Added", oldlinkURL);
return View("AddKBLink", assignkblinkToSkill);
}
}
And my repository methods are:-
public async Task<Skill> FindSkill(int id, params Expression<Func<Skill, object>>[] includeProperties)
{
var query = context.Skills.AsQueryable();
if (includeProperties != null || includeProperties.Count() != 0 || includeProperties[0].Name == "0")
query = includeProperties.Aggregate(query, (current, include) => current.Include(include));
return await query.SingleOrDefaultAsync(a => a.SkillID == id);
}
&
public void AddKBLinkToSkill(AssignKBLinksToSkill assignKBLinkToSkill,string username)
{
var skill = context.Skills.SingleOrDefault(a=>a.SkillID == assignKBLinkToSkill.Skillid);
skill.LinkToKBs.Add(assignKBLinkToSkill.LinkToKB);
skill.Modified = System.DateTime.Now;
skill.ModifiedBy = staffrepo.GetUserIdByUserName(username);
context.Entry(skill).State = EntityState.Modified;
}
Currently I am getting a very strange behavior is that , the list that is returned to the view will not contain the newly added LinkToKB value and it will be replaced by the following value:-
assignkblinkToSkill.LinkToKB.URL = "http://"
so can anyone advice on this please, although I am explicitly retrieving the LinkToKB list from database?
visual studio will how the following at two different stages:-
First this is the newly added LinkToKB:-
Second EF have replace it with the value inside the action method:-
I spend the whole day trying to understand what is going on ... and if i removed these lines:-
assignkblinkToSkill.LinkToKB.URL = "http://";//reset the values , so that user will not get old vlues
assignkblinkToSkill.LinkToKB.Name = String.Empty;
i will get the new up-to-date list correctly (but i need them)..
I have two model classes (Skill & LinktoKB):-
public partial class Skill
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Skill()
{
this.SkillLevels = new HashSet<SkillLevel>();
this.SkillLevelStaffs = new HashSet<SkillLevelStaff>();
this.Customers = new HashSet<Customer>();
this.LinkToKBs = new HashSet<LinkToKB>();
this.SkillVersionHistories = new HashSet<SkillVersionHistory>();
this.Skill1 = new HashSet<Skill>();
this.Skills = new HashSet<Skill>();
}
public int SkillID { get; set; }
public string Name { get; set; }
//code goes here
public virtual SkillStatu SkillStatu { get; set; }
public virtual SkillType SkillType { get; set; }
public virtual ICollection<LinkToKB> LinkToKBs { get; set; }
}
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public LinkToKB()
{
this.Skills = new HashSet<Skill>();
}
public int LinkToKBID { get; set; }
public string URL { get; set; }
public string Name { get; set; }
public virtual ICollection<Skill> Skills { get; set; }
}
and the following viewModel class:-
public class AssignKBLinksToSkill
{
public ICollection<LinkToKB> LinktoKBList { set; get; }
public LinkToKB LinkToKB { set; get; }
public int Skillid { set; get; }
}
In your code there's always one assignkblinkToSkill.LinkToKB instance. When it enters the method it's got some value that you store in the database. Later you re-assign its value to be "http://".
But this is still the instance that you added to the list skillAfterAddingKBLink.LinkToKBs!
You only have to create a new instance in the view model:
assignkblinkToSkill.LinkToKB = new LinkToKB();
assignkblinkToSkill.LinkToKB.URL = "http://";
I've built my Domain model layer, my repository layer, and now I'm working on my DTO layer to be used by a webApi project. I'm in the middle of implementing an Update service method, and I'm wondering about partial updates. Here's my DTO class:
public class FullPersonDto
{
public FullPersonDto()
{
Friends = new List<Person>();
}
public FullPersonDto(Person person)
{
PersonId = person.PersonId;
DateCreated = person.DateCreated;
Details = person.Details;
Friends = new List<Person>();
foreach (Person friend in person.Friends)
{
Friends.Add(new PersonDto(friend));
}
}
[Key]
public int PersonId { get; set; }
[Required]
public virtual DateTime DateCreated { get; set; }
public virtual string Details { get; set; }
public List<Person> Friends { get; set; }
public Person ToEntity()
{
var person = new Person
{
PersonId = PersonId,
DateCreated = (DateTime) DateCreated,
Details = Details,
Friends = new List<Person>()
};
foreach (PersonDto friend in Friends)
{
person.Friends.Add(friend.ToEntity());
}
return person;
}
}
Here's my Update method in my Repository:
public Person UpdatePerson(Person person)
{
var entry = _db.Entry(person);
if (entry.State == EntityState.Detached)
{
var dbSet = _db.Set<Person>();
Person attachedPerson = dbSet.Find(person.PersonId);
if (attachedPerson != null)
{
var attachedEntry = _db.Entry(attachedPerson);
attachedEntry.CurrentValues.SetValues(person); // what if values are null, like ID, or DateCreated?
}
else
{
entry.State = EntityState.Modified;
}
}
SaveChanges();
return person;
}
My question is: What if I only need to update the Details of a person via my webAPI? Is the convention to construct an entire PersonDto and Update the entire object using SetValues, or is there any way I can specify that I only want a single field updated so that I don't have to send a ton of data over the wire (that I don't really need)?
If it is possible to do partial updates, when is it ever good to update the entire entity? Even if I have to update 5/7 properties, it requires that I send old data for 2/7 to re-write so that SetValues doesn't write nulls into my fields from my DTO.
Any help here would be awesome... totally new to this stuff and trying to learn everything right. Thank you.
I've taken similar approach to do optimization, and I've faced same issues with null values when attaching (not just null, you'll have issue with boolean as well). This is what I've come up with:
public static void Update<T>(this DbContext context, IDTO dto)
where T : class, IEntity
{
T TEntity = context.Set<T>().Local.FirstOrDefault(x => x.Id == dto.Id);
if (TEntity == null)
{
TEntity = context.Set<T>().Create();
TEntity.Id = dto.Id;
context.Set<T>().Attach(TEntity);
}
context.Entry(TEntity).CurrentValues.SetValues(dto);
var attribute = dto.GetAttribute<EnsureUpdatedAttribute>();
if (attribute != null)
{
foreach (var property in attribute.Properties)
context.Entry(TEntity).Property(property).IsModified = true;
}
}
That is extension method for DbContext. Here are the interfaces IDTO and IEntity:
public interface IDTO
{
int Id { get; set; }
}
public interface IEntity
{
int Id { get; set; }
Nullable<DateTime> Modified { get; set; }
Nullable<DateTime> Created { get; set; }
}
I'm using my custom EnsureUpdatedAttribute to annotate what properties should always be updated (to deal with nulls / default values not being tracked):
public class EnsureUpdatedAttribute : Attribute
{
public IEnumerable<string> Properties { get; private set; }
public EnsureUpdatedAttribute(params string[] properties)
{
Properties = properties.AsEnumerable();
}
}
And this is a sample of usage:
public class Sample : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public Nullable<DateTime> Modified { get; set; }
public Nullable<DateTime> Created { get; set; }
}
[EnsureUpdated("Active")] /// requirement for entity framework change tracking, read about stub entities
public class SampleDTO : IDTO
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[JsonIgnore] /// How to exclude property from going on the wire / ignored for serialization
public bool Active { get; set; }
}
[HttpPost]
public HttpResponseMessage SaveSample(SampleDTO dto)
{
dto.Active = true;
_ctx.AddModel<Sample>(dto);
_ctx.SaveChanges();
return NoContent();
}
return NoContent() is just extension for returning 204 (NoContent).
Hope this helps.
Theres a few options you have, you can create a stored procedure to update the required parts (I wouldnt do this), or you can manually select the fileds to update on the model before saving the context changes with EF.
Heres an example how to update a specific field:
public void UpdatePerson(int personId, string details)
{
var person = new Person() { Id = personId, Details = details };
db.Persons.Attach(personId);
db.Entry(person).Property(x => x.Details).IsModified = true;
db.SaveChanges();
}
It will depend on your scenario what you want to do, but generally speaking its fine to send your whole entity to be updated, and this is how i would approach your situation potentially changing in the future if needed.