How do I write an edit method for related data - c#

In my mvc application I have a lot of related data, I need to write a method to allow the edit/update of that related data, at the moment my data is handled using a ViewModel. I have attempted to write a method to do this but I can't get it to work correctly and I'm not sure my approach is quite right and so I wanted to get some guidance.
Here is my code:
Controller Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FullVesselViewModel model)
{
var vessel = new tbl_vessels
{
vessel_idx = model.vessel_idx,
vessel_name = model.vessel_name
};
var vessel_spec = new tbl_vessel_spec
{
spec_idx = model.spec_idx,
bhp = model.bhp
}
using (var context = new dataEntities())
{
context.Entry(vessel).State = EntityState.Modified;
context.Entry(vessel_spec).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
}
My initial though was to declare my viewmodel and then break down the two related entities and store them in variables, comitting those variables as modified and then saving my changes. My above approach returns concurrency errors. I have stored the indexes of the entities in a couple of hidden for fields to see if that would help but I've had no luck.
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.vessel_idx)
#Html.HiddenFor(model => model.spec_idx)
...input fields etc ...
}
Is my thinking correct for this sort of scenario or have I over engineered something that can be done simpler?
The error that I get is as follows and is triggered at SaveChanges():
Entity Framework: “Store update, insert, or delete statement affected an unexpected number of rows (0).”

After analyzing your code & found context.SaveChanges() as source of EF exception, you may choose one of these solutions that suitable for your issue.
Change EntityState to Added before saving changes:
using (var context = new dataEntities())
{
context.Entry(vessel).State = EntityState.Added;
context.Entry(vessel_spec).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Index");
}
or use TryUpdateModel inside your controller action after finding data matches, in case you need data update without changing primary key:
var foundvessel = context.tbl_vessels.Where(x => x.vessel_idx == model.vessel_idx).FirstOrDefault();
var foundvspec = context.tbl_vessel_spec.Where(x => x.spec_idx == model.spec_idx).FirstOrDefault();
if (foundvessel != null && foundvspec != null)
{
var vessel = new tbl_vessels
{
vessel_idx = model.vessel_idx,
vessel_name = model.vessel_name
};
var vessel_spec = new tbl_vessel_spec
{
spec_idx = model.spec_idx,
bhp = model.bhp
}
using (var context = new dataEntities())
{
this.TryUpdateModel(vessel);
this.TryUpdateModel(vessel_spec);
context.SaveChanges();
return RedirectToAction("Index");
}
}
EF's generated error indicates an optimistic concurrency exception where you trying to update primary key value of some table(s), i.e. if vessel_idx & spec_idx are primary key from each target table EF treats modifying them as adding new row(s), rather than updates against existing data.
Thus, you need to perform:
EntityState.Added if new primary key has added
EntityState.Modified if existing primary key left unchanged
Any improvements or suggestions welcome.
Additional reference: Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0)."

Related

ASP.NET MVC Deleting row from one table after copying it to another table

I have a case where I am trying to Delete one item from a table but before doing that I want to copy it to another table.
Here is my Delete method:
public ActionResult Delete(int id, Car car)
{
try
{
using (BookCarDBEntities db = new BookCarDBEntities())
{
var carToDelete = db.Cars.FirstOrDefault(c => c.Id == id);
var book = CreateNewBooking(carToDelete);
db.Bookings.Add(book);
db.Cars.Remove(carToDelete);
db.SaveChanges();
return View(book);
}
catch (Exception ex)
{
return View(ex + "error");
}
}
And here is a method which does the conversion from 'Car' table to 'Booking' table:
private object CreateNewBooking(Car car)
{
var bookingCreated = new Booking
{
id = car.Id,
model = car.model,
make = car.make,
price = car.price,
location = car.location
};
return bookingCreated;
}
The problem is that I get an error:
'System.InvalidOperationException': The entity type Booking is not part of the model for the current context.
How can I solve this?
You managed to fix the error (in the comments) but I'll post the solution here for others.
The error:
'System.InvalidOperationException': The entity type Booking is not part of the model for the current context.
can be caused for a couple of reasons.
The entity has become detached or has somehow lost its reference
within the entity framewowrk. This can be resolved by refreshing the
entities. Go to the edmx file, right-click on the designer surface
and selecting Update model from database. If that still fails,
delete the entity (or all entities) in the designer and then update
model from database.
If the table/entity does not exist. In this case create the table/entity and
update the entities.
Then Rebuild after you have updated from database.

Insert into multiple tables using WCF Transactions

I am trying to add an entry into a table and use the primary key of that added entry to create an additional entry into another table.
The error I am getting is
The transaction manager has disabled its support for remote/network
transactions. (Exception from HRESULT: 0x8004D024)
I believe this is caused by creating multiple connections within a single TransactionScope, but I am doing everything within one context / using statement, so I do not believe that I should be receiving this error.
Service
[OperationBehavior(TransactionScopeRequired = true)]
public void CreateGroup(NewGroupData data)
{
var groupRepo = _GroupRepo ?? new InvestigatorGroupRepository();
groupRepo.CreateGroup(data.UserId, data.InvestigatorGroupName, data.HasGameAssignment, data.InstitutionId);
}
Repository
public void CreateGroup(string userId, string investigatorGroupName, bool hasGameAssignment, int institutionId)
{
using (var context = new GameDbContext())
{
var newGroup = new InvestigatorGroup()
{
InvestigatorGroupName = investigatorGroupName,
HasGameAssignment = hasGameAssignment,
InstitutionId = institutionId,
IsTrashed = false
};
int institutionUserId =
context.InstitutionUsers.Where(
iu => !iu.IsTrashed && iu.APUser.UserId == userId && iu.InstitutionId == institutionId).Select(iu => iu.InstitutionUserId).Single();
var newGroupUser = new InvestigatorGroupUser()
{
InstitutionUserId = institutionUserId,
InvestigatorGroup = newGroup,
CreationDate = DateTime.Now
};
context.InvestigatorGroupUsers.Add(newGroupUser);
context.SaveChanges();
}
}
You start with a wrong assumption.
The line...
int newGroupId = context.InvestigatorGroups.Add(newGroup).InvestigatorGroupId;
...will always assign 0 to newGroupId. The Add method only marks the entity for insert, but doesn't actually insert it. Only SaveChanges writes data to the database, not any other method in Entity Framework.
So the assignment...
InvestigatorGroupId = newGroupId,
...is faulty as well. You have to assign the new InvestigatorGroup to a navigation property in InvestigatorGroupUser:
InvestigatorGroup = newGroup,
Add this navigation property to InvestigatorGroupUser if you haven't got it yet.
If you have that, it's enough to execute these lines:
context.InvestigatorGroupUsers.Add(newGroupUser);
context.SaveChanges();
No need to Add the newGroup object too, It will be added by adding newGroupUser.
So if you do that, the only transaction you need is the one that SaveChanges uses internally by default. For the code you show, you don't need a TransactionScope. If this is part of a greater WCF transaction the story may be different, but I think at least you needed some misconceptions to be straightened out.

How to update many to many in Entity Framework with AutoDetectChangesEnabled = false

Please, help me to handle this situation:
I meaningly switched off AutoDetectChangesEnabled and I load my
entities AsNoTracked() meaningly either.
And I can't update many-to-many relationship in this case:
Here is the code of Update method:
public void Update(User user)
{
var userRoleIds = user.Roles.Select(x => x.Id);
var updated = _users.Find(user.Id);
if (updated == null)
{
throw new InvalidOperationException("Can't update user that doesn't exists in database");
}
updated.Name = user.Name;
updated.LastName = user.LastName;
updated.Login = user.Login;
updated.Password = user.Password;
updated.State = user.State;
var newRoles = _roles.Where(r => userRoleIds.Contains(r.Id)).ToList();
updated.Roles.Clear();
foreach (var newRole in newRoles)
{
updated.Roles.Add(newRole);
}
_context.Entry(updated).State = EntityState.Modified;
}
All simple fields, like Name, LastName updated. But the set
of Roles for User doesn't get updated - it stays the same.
I tried loading Roles using
_context.Entry(updated).Collection("Roles").Load();
But I can't update this loaded set in any way.
I searched for similar items but failed to find the answer, thought it definitely already exists.
I'm really sorry for possible dublicate.
PS. I want to add that I don't want to delete or update child entities at all.
A lot of existing answers suggest manually delete / add child entities to database in whole, but it is not suitable for me.
Roles are independent entities, any other user can use them.
I just want to update User_Role table in database, but I can't.

Insert/Update Entity in EF?

I am writing a crawler that should go to a website, extract some data from there and then store it into a db, the thing is the crawler should also update the data that has already been found in a prior run.
The ParseDataPage returns the information parsed from the site in a EF POCO, one of its properties is a unique identifier (which also is the primary key in the db table), how can I tell EF to insert/add the object?
class Program
{
static void Main()
{
var context = (adCreatorEntities) DbContextFactory.GetInstance().GetDbContext<adCreatorEntities>();
var crawler = new DataCrawler();
crawler.Login();
var propertyIds = crawler.GetPropertyIds();
foreach (var id in propertyIds)
{
var poco = crawler.ParseDataPage(id);
context.Properties.Add(poco); //<-- How can I tell EF to update if the record exists or to insert it otherwise??
context.SaveChanges();
}
context.SaveChanges();
if (crawler.LoggedIn)
crawler.Logout();
}
}
You can set the entity state to Modified or Add the entity to the DbSet based on the key's value.
if(entity.propertyId <= 0)
{
context.Properties.Add(poco);
}
else
{
context.Entry(poco).State = EntityState.Modified;
}
This is code for EF5, EF4 is slightly different for setting object state
context.ObjectStateManager.ChangeObjectState(poco, EntityState.Modified);
you can check whether record exists or not using following code,
var entity= dataContext.Properties.Find(b => b.UniqueId == poco.UniqueId);
if (entity== null)
{
dataContext.Properties.Add(poco);
}
else
{
dataContext.Entry(entity).State = EntityState.Modified;
}
dataContext.SaveChanges();

dbset.local updating database: a duplicate value cannot be inserted into a unique index

I am getting an error when calling entities.savechanges() on my EF 4.3.1. My database is a sql ce v4 store and I am coding in the mvvm pattern. I have a local version of my context that I send to an observable collection and modify etc. This works fine, and when I call savechanges() when no rows exist in the database the objects persist fine. When I reload the application, the objects are populated in my listbox as they should, however if I add another object and call savechanges() I get an error saying that a duplicate value cannot be inserted into a unique index.
From my understanding it means that the context is trying to save my entities to the datastore, but it seems to be adding my untouched original objects as well as the new one. I thought it would leave them alone, since their state is unchanged.
private void Load()
{
entities.Properties.Include("Images").Load();
PropertyList = new ObservableCollection<Property>();
PropertyList = entities.Properties.Local;
//Sort the list (based on previous session stored in database)
var sortList = PropertyList.OrderBy(x => x.Sort).ToList();
PropertyList.Clear();
sortList.ForEach(PropertyList.Add);
propertyView = CollectionViewSource.GetDefaultView(PropertyList);
if (propertyView != null) propertyView.CurrentChanged += new System.EventHandler(propertyView_CurrentChanged);
private void NewProperty()
{
try
{
if (PropertyList != null)
{
Property p = new Property()
{
ID = Guid.NewGuid(),
AgentName = "Firstname Lastname",
Address = "00 Blank Street",
AuctioneerName = "Firstname Lastname",
SaleTitle = "Insert a sales title",
Price = 0,
NextBid = 0,
CurrentImage = null,
Status = "Auction Pending",
QuadVis = false,
StatVis = false, //Pause button visibility
Sort = PropertyList.Count + 1,
};
PropertyList.Add(p);
SaveProperties();
}
private void SaveProperties()
{
try
{
foreach (var image in entities.Images.Local.ToList())
{
if (image.Property == null)
entities.Images.Remove(image);
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
entities.SaveChanges();
}
Without commenting on all the code here this is the bit that's causing the specific problem you bring up:
//Sort the list (based on previous session stored in database)
var sortList = PropertyList.OrderBy(x => x.Sort).ToList();
PropertyList.Clear();
sortList.ForEach(PropertyList.Add);
This code:
Starts with entities that have been queried and are being tracked by the context as Unchanged entities. That is, entities that are known to already exist in the database.
Creates a new sorted list of these entities.
Calls Clear on the local collection causing each tracked entity to be marked as deleted and removed from the collection.
Adds each entity back to the context putting it now in an Added state meaning that it is new and will be saved to the database when SaveChanges is called,
So effectively you have told EF that all the entities that exist in the database actually don't exist and need to be saved. So it tries to do this and it results in the exception you see.
To fix this don't clear the DbContext local collection and add entities back. Instead you should sort in the view using the local collection to back the view.
It sounds like you're adding the existing entities to the context (which marks them for insertion) instead of attaching them (which marks them as existing, unmodified).
I'm also not sure that new Guid() isn't returning the same guid... I always use Guid.NewGuid() http://msdn.microsoft.com/en-us/library/system.guid.newguid.aspx

Categories

Resources