MVC3 Entity Framework Code First Updating Subset Related List of Items - asp.net-mvc-3

I have a table of data with a list of key value pairs in it.
Key Value
--------------------
ElementName PrimaryEmail
Email someemail#gmail.ca
Value Content/Images/logo-here.jpg
I am able to generate new items on my client webpage. When, I create a new row on the client and save it to the server by executing the following code the item saves to the database as expected.
public ViewResult Add(CardElement cardElement)
{
db.Entry(obj).State = EntityState.Added;
db.SaveChange();
return Json(obj);
}
Now, when I want to delete my objects by sending another ajax request I get a failure.
public void Delete(CardElement[] cardElements)
{
foreach (var cardElement in cardElements)
{
db.Entry(cardElement).State = EntityState.Deleted;
}
db.SaveChanges();
}
This results in the following error.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I have tried other ways of deleting including find by id remove and attach and delete but obviously I am approaching in the right fashion.

I am not sure what is causing your issue, but I tend to structure my deletes as follows:
public void Delete(CardElement[] cardElements)
{
foreach (var cardElement in cardElements)
{
var element = db.Table.Where(x => x.ID == cardElement.ID).FirstOrDefault();
if(element != null)
db.DeleteObject(element);
}
db.SaveChanges();
}
although I tend to do database first development, which may change things slightly.
EDIT: the error you are receiving states that no rows were updated. When you pass an object to a view, then pass it back to the controller, this tends to break the link between the object and the data store. That is why I prefer to look up the object first based on its ID, so that I have an object that is still linked to the data store.

Related

How to perform deletion of entities based on a list of ids

I am just starting with linq and entity framework in general and I have a question that may seem naive to all of the advanced users!
I have the following code :
var allDocuments = (from i in companyData.IssuedDocuments select i.IssuedDocumentId).ToList<int>();
var deletedDocuments = allDocuments.Except(updatedDocuments);
and I need to delete all the entities in companyData that their id is stored in deletedDocuments in a disconnected scenario.
Could you please show me a way to do this in an efficient manner?
You could avoid fetching all the ids by specifying you only want deleted ids like this:
var deletedIds = from i in companyData.IssuedDocuments
where !updatedIds.Contains(i.IssuedDocumentId)
select i.IssuedDocumentId
Now if companyData.IssuedDocuments is a DbSet you can tell EF to delete them like this:
foreach (var id in deletedIds)
{
var entity = new MyEntity { Id = id };
companyData.IssuedDocuments.Attach(entity);
companyData.IssuedDocuments.Remove(entity);
}
dbContext.SaveChanges();
This will issue multiple DELETE statements to the database without fetching the full entities into memory.
If companyData.IssuedDocuments is your repository then you could load the full entities instead of just the ids:
var deleted = from i in companyData.IssuedDocuments
where !updatedIds.Contains(i.IssuedDocumentId)
select i
foreach (var entity in deleted)
companyData.IssuedDocuments.Delete(entity);
dbContext.SaveChanges();
Again EF issues multiple DELETE statements to the database
If you can upgrade then EF6 has introduced a RemoveRange method on the DbSet that at you could look at. It may send a single DELETE statement to the database - I haven't tried it yet.
If performance is still an issue then you have to execute sql.
References:
RemoveRange
Deleting an object without retrieving it
How should I remove all elements in a DbSet?
companyData.RemoveAll(x=>deletedDocuments.Contains(x.Id));
I suppose the companyData is a IEnumerable type. The type T contains an Id property, which is the Id of the data. Then deletedDocuments contains the ids of all the documents that we want to remove.
One thing that's important and I should note it here is that the deletion of the documents happens in memory and it doesn't execute it in a db. Otherwise you should provide us with the version of entity framework you use and how you access you implelemnt your CRUD operations against your db.
Firstly I would like to thank you all for your suggestions.
I followed Christos Paisios suggestion but I was getting all kinds of exceptions when I was trying to persist the changes to the DB and the way that I finally managed to solve the issues was by adding the following override in my DbContext class
public override int SaveChanges()
{
var orphanedResponses = ChangeTracker.Entries().Where(
e => (e.State == EntityState.Modified || e.State == EntityState.Added) &&
e.Entity is IssuedDocument &&
e.Reference("CompanyData").CurrentValue == null);
foreach (var orphanedResponse in orphanedResponses)
{
IssuedDocuments.Remove(orphanedResponse.Entity as IssuedDocument);
}
return base.SaveChanges();
}

Entity Framework LINQ insert command failing. MVC3

Hi I'm trying to do a basic update based on an id using Linq and the entity framework. I'm very new to this but I do not see the problem.
My entity class object is declared at the controller level.
gwwbnEntities db = new gwwbnEntities();
The Method grabs a querystring id and updates the user's registration status who is represented by that id.
public ActionResult ConfirmedAccount(int id)
{
var q = from u in db.user_registration
where u.id == id && u.reg_status == null
select u;
if (q.Any())
{
foreach(var item in q){
user_registration user = item;
user.reg_status = 202;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
}
return View();
}
else
{
return RedirectToAction("RegistrationError");
}
}
Any help would be greatly appreciated! Again everything works and populates correctly, but the context object.SaveChanges() method fails everytime.
Thanks guys!
The exception you are seeing is because you have an open data reader (foreach) and you are trying to create transaction (EF does it for you) in SaveChanges(). Call SaveChanges outside the loop.
In addtion: Don't set the state to Modified - EF will detect that properties changed and will automatically set the state accordingly. You may want to do .ToList() on the q before doing anything. At the moment you are sending to queries to the database (one for .Any() and one to get entities). If you do .ToList() you will send only one query that brings entities but .Any() would be called on the list not on the database so it will be much faster and there is no trip to the database. Also ToList() force query evaluation so your foreach loop will not keep the data reader open as it will iterate on the list.

How can I create temporary records of Linq-To-Sql types without causing duplicate key problems?

I have code that generates records based on my DataGridView. These records are temporary because some of them already exist in the database.
Crop_Variety v = new Crop_Variety();
v.Type_ID = currentCropType.Type_ID;
v.Variety_ID = r.Cells[0].Value.ToString();
v.Description = r.Cells[1].Value.ToString();
v.Crop = currentCrop;
v.Crop_ID = currentCrop.Crop_ID;
Unfortunately in this little bit of code, because I say that v.Crop = currentCrop,
now currentCrop.Crop_Varieties includes this temporary record. And when I go to insert the records of this grid that are new, they have a reference to the same Crop record, and therefore these temporary records that do already exist in the database show up twice causing duplicate key errors when I submit.
I have a whole system for detecting what records need to be added and what need to be deleted based on what the user has done, but its getting gummed up by this relentless tracking of references.
Is there a way I can stop Linq-To-Sql from automatically adding these temporary records to its table collections?
I would suggest revisiting the code that populates DataGridView (grid) with records.
And then revisit the code that operates on items from a GridView, keeping in mind that you can grab bound item from a grid row using the following code:
public object GridSelectedItem
{
get
{
try
{
if (_grid == null || _grid.SelectedCells.Count < 1) return null;
DataGridViewCell cell = _grid.SelectedCells[0];
DataGridViewRow row = _grid.Rows[cell.RowIndex];
if (row.DataBoundItem == null) return null;
return row.DataBoundItem;
}
catch { }
return null;
}
}
It is also hard to understand the nature of Crop_Variety code that you have posted. As the Crop_Variety seems to be a subclass of Crop. This leads to problems when the Crop is not yet bound to database and potentially lead to problems when you're adding Crop_Variety to the context.
For this type of Form application I normally have List _dataList inside form class, then the main grid is bound to that list, through ObjectBindingList or another way. That way _dataList holds all data that needs to be persisted when needed (user clicked save).
When you assign an entity object reference you are creating a link between the two objects. Here you are doing that:
v.Crop = currentCrop;
There is only one way to avoid this: Modify the generated code or generate/write your own. I would never do this.
I think you will be better off by writing a custom DTO class instead of reusing the generated entities. I have done both approaches and I like the latter one far better.
Edit: Here is some sample generated code:
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="RssFeed_RssFeedItem", Storage="_RssFeed", ThisKey="RssFeedID", OtherKey="ID", IsForeignKey=true, DeleteOnNull=true, DeleteRule="CASCADE")]
public RssFeed RssFeed
{
get
{
return this._RssFeed.Entity;
}
set
{
RssFeed previousValue = this._RssFeed.Entity;
if (((previousValue != value)
|| (this._RssFeed.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._RssFeed.Entity = null;
previousValue.RssFeedItems.Remove(this);
}
this._RssFeed.Entity = value;
if ((value != null))
{
value.RssFeedItems.Add(this);
this._RssFeedID = value.ID;
}
else
{
this._RssFeedID = default(int);
}
this.SendPropertyChanged("RssFeed");
}
}
}
As you can see the generated code is establishing the link by saying "value.RssFeedItems.Add(this);".
In case you have many entities for wich you would need many DTOs you could code-generate the DTO classes by using reflection.

Auditing in Entity Framework

After going through Entity Framework I have a couple of questions on implementing auditing in Entity Framework.
I want to store each column values that is created or updated to a different audit table.
Right now I am calling SaveChanges(false) to save the records in the DB(still the changes in context is not reset). Then get the added | modified records and loop through the GetObjectStateEntries. But don't know how to get the values of the columns where their values are filled by stored proc. ie, createdate, modifieddate etc.
Below is the sample code I am working on it.
// Get the changed entires( ie, records)
IEnumerable<ObjectStateEntry> changes = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
// Iterate each ObjectStateEntry( for each record in the update/modified collection)
foreach (ObjectStateEntry entry in changes)
{
// Iterate the columns in each record and get thier old and new value respectively
foreach (var columnName in entry.GetModifiedProperties())
{
string oldValue = entry.OriginalValues[columnName].ToString();
string newValue = entry.CurrentValues[columnName].ToString();
// Do Some Auditing by sending entityname, columnname, oldvalue, newvalue
}
}
changes = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added);
foreach (ObjectStateEntry entry in changes)
{
if (entry.IsRelationship) continue;
var columnNames = (from p in entry.EntitySet.ElementType.Members
select p.Name).ToList();
foreach (var columnName in columnNames)
{
string newValue = entry.CurrentValues[columnName].ToString();
// Do Some Auditing by sending entityname, columnname, value
}
}
Here you have two basic options:
Do it at the database level
Do it in the c# code
Doing it at the data base level, means using triggers. In that case there is no difference if you are using enterprise library or another data access technology.
To do it in the C# code you would add a log table to your datamodel, and write the changes to the log table. When you do a save changes both the changes to the data and the information which you wrote to the log table would be saved.
Are you inserting the new record using a stored proc? If not (i.e. you are newing up an object, setting values, inserting on submit and then saving changes the new object id will be automatically loaded into the id property of the object you created. If you are using a stored proc to do the insert then you need to return the ##IDENTITY from the proc as a return value.
EX:
StoreDateContext db = new StoreDataContext(connString);
Product p = new Product();
p.Name = "Hello Kitty Back Scratcher";
p.CategoryId = 5;
db.Products.Add(p);
try
{
db.SaveChanges();
//p.Id is now set
return p.Id;
}
finally
{
db.Dispose;
}

How do I delete records from a child collection in LINQ to SQL?

I have two tables in my database connected by foreign keys: Page (PageId, other data) and PageTag (PageId, Tag). I've used LINQ to generate classes for these tables, with the page as the parent and the Tag as the child collection (one to many relationship). Is there any way to mark PageTag records for deletion from the database from within the Page class?
Quick Clearification:
I want the child objects to be deleted when the parent DataContext calls SubmitChanges(), not before. I want TagString to behave exactly like any of the other properties of the Page object.
I would like to enable code like the following:
Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";
//Changes have not been written to the database at this point.
mDataContext.SubmitChanges();
//All changes should now be saved to the database.
Here is my situation in detail:
In order to make working with the collection of tags easier, I've added a property to the Page object that treats the Tag collection as a string:
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
PageTags.Clear();
foreach (string tag in tags) {
PageTag pageTag = new PageTag();
pageTag.Tag = tag;
PageTags.Add(pageTag);
}
}
}
Basically, the idea is that when a string of tags is sent to this property, the current tags of the object are deleted and a new set is generated in their place.
The problem I'm encountering is that this line:
PageTags.Clear();
Doesn't actually delete the old tags from the database when changes are submitted.
Looking around, the "proper" way to delete things seems to be to call the DeleteOnSubmit method of the data context class. But I don't appear to have access to the DataContext class from within the Page class.
Does anyone know of a way to mark the child elements for deletion from the database from within the Page class?
After some more research, I believe I've managed to find a solution. Marking an object for deletion when it's removed from a collection is controlled by the DeleteOnNull parameter of the Association attribute.
This parameter is set to true when the relationship between two tables is marked with OnDelete Cascade.
Unfortunately, there is no way to set this attribute from within the designer, and no way to set it from within the partial class in the *DataContext.cs file. The only way to set it without enabling cascading deletes is to manually edit the *DataContext.designer.cs file.
In my case, this meant finding the Page association, and adding the DeleteOnNull property:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
public Page Page
{
...
}
And adding the DeleteOnNull attribute:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
public Page Page
{
...
}
Note that the attribute needed to be added to the Page property of the PageTag class, not the other way around.
See also:
Beth Massi -- LINQ to SQL and One-To-Many Relationships
Dave Brace -- LINQ to SQL: DeleteOnNull
Sorry, my bad. That won't work.
It really looks like you need to be doing this in your repository, rather than in your Page class. There, you have access to your original data context.
There is a way to "attach" the original data context, but by the time you do that, it has become quite the code smell.
Do you have a relationship, in your Linq to SQL entity diagram, linking the Page and PageTags tables? If you don't, that is why you can't see the PageTags class from the Page class.
If the foreign key in the PageTags database table is set to Allow Nulls, Linq to SQL will not create the link when you drag the tables into the designer, even if you created a relationship on the SQL Server.
This is one of those areas where OR mapping can get kind of hairy. Providing this TagString property makes things a bit more convenient, but in the long run it obfuscates what is really happening when someone utilizes the TagString property. By hiding the fact that your performing data modification, someone can very easily come along and set the TagString without using your Page entity within the scope of a DataContext, which could lead to some difficult to find bugs.
A better solution would be to add a Tags property on the Page class with the L2S model designer, and require that the PageTags be edited directly on the Tags property, within the scope of a DataContext. Make the TagString property read only, so it can be genreated (and still provide some convenience), but eliminate the confusion and difficulty around setting that property. This kind of change clarifies intent, and makes it obvious what is happening and what is required by consumers of the Page object to make it happen.
Since Tags is a property of your Page object, as long as it is attached to a DataContext, any changes to that collection will properly trigger deletions or insertions in the database in response to Remove or Add calls.
Aaron,
Apparently you have to loop thru your PageTag records, calling DeleteOnSubmit for each one. Linq to SQL should create an aggregate query to delete all of the records at once when you call SubmitChanges, so overhead should be minimal.
replace
PageTags.Clear();
with
foreach (PageTag tag in PageTags)
myDataContext.DeleteOnSubmit(tag);
Aaron:
Add a DataContext member to your PageTag partial class.
partial class PageTag
{
DataClassesDataContext myDataContext = new DataClassesDataContext();
public string TagString {
..etc.
Larger code sample posted at Robert Harvey's request:
DataContext.cs file:
namespace MyProject.Library.Model
{
using Tome.Library.Parsing;
using System.Text;
partial class Page
{
//Part of Robert Harvey's proposed solution.
MyDataContext mDataContext = new TomeDataContext();
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
//Original code, fails to mark for deletion.
//PageTags.Clear();
//Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
foreach (PageTag tag in PageTags) {
mDataContext.PageTags.DeleteOnSubmit(tag);
}
foreach (string tag in tags) {
PageTag PageTag = new PageTag();
PageTag.Tag = tag;
PageTags.Add(PageTag);
}
}
}
private bool mIsNew;
public bool IsNew {
get {
return mIsNew;
}
}
partial void OnCreated() {
mIsNew = true;
}
partial void OnLoaded() {
mIsNew = false;
}
}
}
Repository Methods:
public void Save() {
mDataContext.SubmitChanges();
}
public Page GetPage(string pageName) {
Page page =
(from p in mDataContext.Pages
where p.FileName == pageName
select p).SingleOrDefault();
return page;
}
Usage:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(string pageName, FormCollection formValues) {
Page updatedPage = mRepository.GetPage(pageName);
//TagString is a Form value, and is set via UpdateModel.
UpdateModel(updatedPage, formValues.ToValueProvider());
updatedPage.FileName = pageName;
//At this point NO changes should have been written to the database.
mRepository.Save();
//All changes should NOW be saved to the database.
return RedirectToAction("Index", "Pages", new { PageName = pageName });
}

Resources