I have ONE entity that i want to update without updating its List of MANY entity. Im using Code-First But i cant get it to work...
Im using Ninject and everything is working except my update...
//Entities
public class A
{
public int AId { get; set; }
public string Name { get; set; }
}
public class B
{
public int BId { get; set; }
public string Name { get; set; }
public virtual List<A>ListOfAs { get; set; }
}
//Interface
private EFDbContext context = new EFDbContext();
public IQueryable<B> Bs
{
get { return context.B; }
}
public void SaveBs(B b)
{
if (b.Id== 0)
{
context.B.Add(b);
context.SaveChanges();
}
*//here i wanna call:
context.Entity(b).State = EntityState.Modified;
BUT VS dont let me... I probably missing something out...*
context.SaveChanges();
the Save method is working when i want to just add a new object to my database. But the update wont change anything...
I would appricate if anyone could tell me what im missing out...
/Thx J
Use The Following Criteria, This work for me
public BuyerInformation Update(BuyerInformation objBuyerInformation)
{
context.BuyerInformation.Attach(objBuyerInformation);
context.Entry(objBuyerInformation).State = EntityState.Modified;
context.SaveChanges();
return objBuyerInformation;
}
if (b.Id== 0)
{
context.B.Add(b);
}
else
{
context.B.Attach(b);
}
context.SaveChanges();
Related
I'm attempting to create an Audit Log for my MVC, Entity Framework website project. I've been able to subscribe to SaveChanges() in my DBContext (and save to my database through another DBContext but same database).
My two questions in the end are:
What does if (!entry.IsRelationship) do exactly? I have a ViewModel that calculates this as True when Saving and another as False. I would expect this to move into the rest of my method to save in the Audit Log.
How can I get the full Namespace of my Object being modified? I was using this: entry.Entity.ToString() but doesn't seem to work when Saving/Editing from a View Model (details below)
Here is a basic setup that I have thus far (Album object/controller works, but AlbumView doesn't):
Ablum class:
public class Album : BaseObject //BaseObject has a few properties, one is Oid (Guid)
{
public string Name { get; set; }
[Column(TypeName = "varchar(MAX)")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name="Genres")]
public virtual ICollection<AlbumsGenres> AlbumGenres { get; set; }
[Display(Name="Artists")]
public virtual ICollection<AlbumsArtists> AlbumArtists { get; set; }
}
AblumView class:
public class AlbumView
{
[ScaffoldColumn(false)]
public Guid Oid { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "varchar(MAX)")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Genres")]
public virtual List<AlbumsGenres> AlbumGenres { get; set; }
[Display(Name = "Artists")]
public virtual List<AlbumsArtists> AlbumArtists { get; set; }
}
AlbumsController (Audit works with something like this):
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges(); //This is where SaveChanges() takes over (see below)
return RedirectToAction("Index");
}
return View(album);
}
AlbumsViewController:
public ActionResult Edit(Guid id, AlbumView albumViewModel)
{
//Omitting setup...
//Album gets updated
Album album = db.Albums.Find(id);
album.Name = albumViewModel.Name;
album.Description = albumViewModel.Description;
//Other Objects are also updated, just an example:
albumArtists = new AlbumsArtists();
albumArtists.Oid = Guid.NewGuid();
albumArtists.Album = db.Albums.Find(id);
albumArtists.Artist = db.Artists.Find(item.Artist.Oid);
//In the end it calls:
db.SaveChanges();
//Omitting other stuff...
}
On db.SaveChanges() within my DbContext:
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext() : base("name=DefaultConnection") { }
public System.Data.Entity.DbSet<ContentPub.Models.Music.Album> Albums { get; set; }
//Other DBSet objects...
public DbSet Set(string name)
{
return base.Set(Type.GetType(name));
}
public override int SaveChanges()
{
ApplicationLogDBContext logDb = new ApplicationLogDBContext();
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)
{
Guid oid = Guid.Empty;
try
{
if (!entry.IsRelationship) //I don't understand this (first of my two questions)
{
switch (entry.State)
{
//Removed other cases
case EntityState.Modified:
{
oid = (Guid)entry.EntityKey.EntityKeyValues[0].Value;
//This is the area that I am having issues (second of the two questions)
//Below will work when I call db.SaveChanges() from the AlbumsController,
//'entry.Entity.ToString()' will get 'x.Models.Music.Albums' and begin a query
var query = this.Set(entry.Entity.ToString()).AsNoTracking().Where("Oid == #0", oid);
//The issue with the above is when I have a ViewModel, returns something like
// = System.Data.Entity.DynamicProxies.Album_AF81C390156ACC8283ECEC668AFB22C4AD621EF70F8F64641D56852D19755BF3
//If the proper Namespace is returned, the next line works and Audit continues
var query = this.Set(entry.EntitySet.ElementType.ToString()).AsNoTracking().Where("Oid == #0", oid);
//Does a bunch of AuditLog stuff if the above issue doesn't fail
break;
}
}
}
}
catch (Exception ex)
{
throw new Exception("Log Error (" + entry.Entity.ToString() + ") - " + ex.ToString());
}
}
return base.SaveChanges();
}
}
entry.Entity.ToString() will return something like:
System.Data.Entity.DynamicProxies.Album_AF81C390156ACC8283ECEC668AFB22C4AD621EF70F8F64641D56852D19755BF3
In the AlbumView I am updating Album, and a bunch of other Objects. Not sure why it isn't returning x.Models.Music.Albums, is there a work-around, can someone explain or point me to other resources that I haven't found yet?
While it isn't the most efficient solution, it still is a solution for now.
I was able to do the following inside my db.SaveChanges() method:
//When AlbumView .BaseType was able to return x.Models.Music.Album
string strNamespace = entry.Entity.GetType().BaseType.ToString();
//Needed this if I was updating just an Object (ie: Album),
//would be nice to make something more concret
if (strNamespace == "x.Models.Core.BaseObject")
strNamespace = entry.Entity.ToString();
//Continuing code
var query = this.Set(strNamespace).AsNoTracking().Where("Oid == #0", oid);
Found the answer here from another Question that I had not found before posting this question
I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.
I have added a function in Entity Framework and I am trying to understand why it wants to return an int instead of a List<string>.
I added the function to entity framework without an issue and once added and Validated the Context file looked as below:
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<AppName> AppNames { get; set; }
public DbSet<AppStatus> AppStatus { get; set; }
public DbSet<Audit> Audits { get; set; }
public DbSet<EntryLog> EntryLogs { get; set; }
public DbSet<LogType> LogTypes { get; set; }
public DbSet<ModuleName> ModuleNames { get; set; }
public DbSet<Trace> Traces { get; set; }
public DbSet<Error> Errors { get; set; }
public virtual int GET_ALL_APPS()
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GET_ALL_APPS");
}
}
I am calling the function below :
public List<string> GetApplicationNames()
{
using (ComData.Entities db = new ComData.Entities())
{
return db.GET_ALL_APPS();
}
}
and here is the function that I added:
create or replace FUNCTION GET_ALL_APPS RETURN SYS_REFCURSOR
AS
PO_RESULT SYS_REFCURSOR;
BEGIN
OPEN PO_RESULT FOR
SELECT UNIQUE
APP_NAME
FROM
LG_ENTRY_BASE_LOG;
RETURN PO_RESULT;
END;
Does anyone know why Entity Framework would look for an int to come back instead of a List<string>
EDIT: The solution mentioned in the possible duplicate does not work because it involves T-SQL. This is PL/SQL and there is no equivalent to SET NOCOUNT ONwhen used in a function.
I found the answer...
I ended up changing:
public List<string> GetApplicationNames()
{
using (ComData.Entities db = new ComData.Entities())
{
return db.GET_ALL_APPS();
}
}
to this:
public IQueryable<List<string>> GetApplicationNames()
{
using (ComData.Entities db = new ComData.Entities())
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<List<string>>("GET_ALL_APPS").ToList().AsQueryable();
}
}
Here is the link that I used to find the answer:
EDIT: While the above worked for me, I found a better way to solve the problem above this morning.
Double click the edmx file and Right Click the open space of the model designer and go into the model browser.
Right Click on the function that you are having a problem with and Click on Properties.
There is a option called Return Type to set what is being handed back...
I have created an ASP.NET MVC5 sample project. I created my entities and from that, scaffolded the controllers for CRUD operations. I can only edit the POD members with the scaffolded code. I want to be able to add/remove related entities.
With my current code, when I click save there is no error but no related entities are modified (POD data is modified though). For example, if I wanted to remove all players from the account, they aren't removed. What am I doing wrong?
How can I remove/add related entities and push those changes to the database?
Here is the form:
Here is the action to update the entity:
public async Task<ActionResult> Edit([Bind(Include = "Account,Account.AccountModelId,Account.Name,Account.CreatedDate,SelectedPlayers")] AccountViewModel_Form vm){
if (ModelState.IsValid){
if (vm.SelectedPlayers != null){
vm.Account.PlayerModels = db.PlayerModels.Where(p => p.AccountModel.AccountModelId == vm.Account.AccountModelId).ToList();
foreach (var player in vm.Account.PlayerModels){
player.AccountModel = null;
db.Entry(player).State = EntityState.Modified;
}
vm.Account.PlayerModels.Clear();
foreach (var player_id in vm.SelectedPlayers){
var player = db.PlayerModels.Where(p => p.PlayerModelId == player_id).First();
vm.Account.PlayerModels.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}
db.Entry(vm.Account).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vm);
}
Here are the models:
public class AccountViewModel_Form{
public AccountModel Account { get; set; }
public HashSet<Int32> SelectedPlayers { get; set; }
public virtual List<PlayerModel> PlayersList { get; set; }
}
public class AccountModel{
public AccountModel(){
PlayerModels = new HashSet<PlayerModel>();
}
public Int32 AccountModelId { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<PlayerModel> PlayerModels { get; set; }
}
public class PlayerModel{
public Int32 PlayerModelId { get; set; }
public float Gold { get; set; }
public string Name { get; set; }
public virtual AccountModel AccountModel { get; set; }
}
I'm basically lost. I can't find any examples in how to update related data. Could someone point me in the right direction?
I come from Symfony (PHP Framework) background. I thought it would be easier but I have been having problems.
Basically I was missing the Attach function and that I had to force the load on the collection to make it work.
I found how to attach a non-attached entity here: Model binding in the controller when form is posted - navigation properties are not loaded automatically
When you post the data, the entity is not attached to the context, and when you try to save changes to a complex entity, the context makes a mess.
The code is a little different because I was trying to make it work at home. But it is essentially the same models.
public ActionResult Edit(AccountEditViewModel vm)
{
if (ModelState.IsValid)
{
//I was missing these 2 important lines...
db.Accounts.Attach(vm.Account);
db.Entry(vm.Account).Collection(a => a.Players).Load();
if (vm.SelectedPlayers != null)
{
foreach (var player in vm.Account.Players.ToList())
{
if (vm.SelectedPlayers.Contains(player.Id) == false)
{
player.Account = null;
vm.Account.Players.Remove(player);
db.Entry(player).State = EntityState.Modified;
vm.SelectedPlayers.Remove(player.Id);
}
}
foreach (var player_id in vm.SelectedPlayers)
{
var player = db.Players.Where(p => p.Id == player_id).First();
player.Account = vm.Account;
vm.Account.Players.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}else
{
vm.Account.Players.Clear();
}
db.Entry(vm.Account).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}
I'm using EF4.1 for the first time (so be patient with me) but I just cant get to grips with how I can add new items to a sub collection of an object and then save the object.
For example, with the classes below, I can initially save the TravelTicket (containing multiple People) into my database, but as soon as I add a new person and then try to save the TravelTicket again I get:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Can anyone help?
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual List<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name{ get; set; }
}
EDITED: All relevant code added as requested:
Domain Models:
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
The DB Context:
public class TicketContext : DbContext
{
public TicketContext()
: base("TicketStore")
{ }
public DbSet<TravelTicket> TravelTickets { get; set; }
public DbSet<Person> People { get; set; }
}
The Repository (relevant methods only):
public class TicketRepository : ITicketRepository
{
TicketContext context = new TicketContext();
public void InsertOrUpdate(TravelTicket quoteContainer)
{
if (quoteContainer.Id == default(int))
{
// New entity
context.TravelTickets.Add(quoteContainer);
}
else
{
// Existing entity
context.Entry(quoteContainer).State = EntityState.Modified;
}
}
public void Save()
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
}
public interface ITicketRepository
{
void InsertOrUpdate(TravelTicket travelTicket);
void Save();
}
The consuming (example) MVC Controller code:
public class TicketSaleController : Controller
{
private readonly ITicketRepository ticketRepository;
public TicketSaleController()
: this(new TicketRepository())
{
}
public TicketSaleController(ITicketRepository ticketRepository)
{
this.ticketRepository = ticketRepository;
}
public ActionResult Index()
{
TravelTicket ticket = new TravelTicket();
ticket.Destination = "USA";
List<Person> travellers = new List<Person>();
travellers.Add(new Person { Name = "Tom" });
travellers.Add(new Person { Name = "Dick" });
travellers.Add(new Person { Name = "Harry" });
ticket.Members = travellers;
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Session["Ticket"] = ticket;
return RedirectToAction("Next");
}
public ActionResult Next()
{
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
return View();
}
}
The call "ticketRepository.InsertOrUpdate(ticket);" on the "Next" method causes the exception:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
FURTHER EDIT: If I pull the object back from the database after its been saved instead of pulling the object from the session, adding the 2 new persons works OK:
Works:
TravelTicket ticket = ticketRepository.Find(ticketId);
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Doesn't Work:
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
I'd need to see the code you are using to add items and then persist them. Until that a few generic advice.
It seems like you're using a long-living context to do your stuff. It's a good practice to use short living context, like this:
Instance context
Do a single operation
Dispose the context
Rinse and repeat for every operation you have to do. While following this good practice, you could be indirectly solving your problem.
Again, for more specific help, please post the code you're using ;)
In your mapping class for person, you may need do something like this
Property(p => p.Id)
.StoreGeneratedPattern = StoreGeneratedPattern.Identity;