Efficient way to "apply to all" with EF - c#

I have a search page which allows users to search for an item and add details to an item. I am working on allowing users to apply the same details to several items. The user will check all the items he would like to apply the same changes to and click a Comment button. This will send a list of items checked List<int> ids and the particular item clicked (to use as template in case it already exists) int id.
From here the user will edit the comment however he wishes and save. How will the HttpPost Edit action look? It is required to add a comment to the database if it doesn't exist for that item already, otherwise, overwrite what exists. Below is what I have come up with (basic outline). The problem I see with this is that it requires poking the database for every item I want the changes applied to. There has to be a better way to do this.
[HttpPost]
public ActionResult Edit(CommentVM model)
{
if (ModelState.IsValid)
{
foreach(int i in model.ids)
{
Comment comment = _db.Comments.Find(i);
if(comment == null){
//Create and add
{
else{
comment.Text = model.Text;
_db.Entry(comment).State = EntityState.Modified;
}
}
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}

The problem I see with this is that it requires poking the database
for every item I want the changes applied to.
You needn't worry about this. The database gets called once when you save your changes. The looping affects only the objects in memory. As for your code you seem to be on the right track, but I do have one potential suggestion. Given your different steps for modifying or creating a comment, I assume that you have written and stored your comments as distinct entities which are linked by id to your items. If this is the case, I would suggest making "Comment" a property of the item, so that way you simply write to the property and simplify the process as a whole.

I ended up using a sort of homebrew method. Since both cases (add entry and edit entry needed to be handled separately, I handled them as such.
List<int> InDB = _db.Comments
.Where(r => model.ids.Contains(r.id))
.Select(r => r.id)
.ToList();
List<int> diff = model.ids.Except(InDB).ToList();
//Loop through items to ADD
foreach (int i in diff)
{
Comment comment = new Comment { Text = model.Text, id = i };
_db.Comments.Add(comment);
}
//Loop through items to edit
foreach (int i in InDB)
{
Comment comment = new Comment { Text = model.Text, id = i };
_db.Entry(comment).State = EntityState.Modified;
}

Related

How to put a value from array to another list in c#

Can anyone help me, guys?
this foreach is always error Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
even though I have given index 0, what's the solution
var store = _cartCollection.Find(x => x.id_cart == entity.id_cart).ToList();
int index = -1;
foreach (var cart in store)
{
var b = index++; // it shows
entity.cart[0].nama_produk = cart.nama_produk;
entity.cart[0].jumlah = cart.jumlah;
entity.cart[0].harga = cart.harga;
entity.cart[0].subtotal = cart.subtotal;
entity.cart[0].notes = cart.notes;
}
here is the class object
enter image description here
I think I declared the entity cart array / list incorrectly, do you know the correct method?
please dont be mean im a newbie
I am not sure what your exact issue is here, but I see a few "code smells" that might be causing the issue:
In this code block:
var store = _cartCollection.Find(x => x.id_cart == entity.id_cart).ToList();
int index = -1;
foreach (var cart in store)
It seems that you are retrieving a single cart based on its ID and then doing something with it. If I have that right, you can accomplish this much more simply with something like this:
var cart = _cartCollection.Find(x => x.id_cart == entity.id_cart).FirstOrDefault();
Then you don't need the for loop at all... just use the one cart you found.
On that note, you should also probably check cart for null... in case it doesn't exist... but that is up to your specific use case.
index is being incremented for each step in the loop... but its value is never used. Unless you have a reason to use it, I recommend just deleting it entirely... unless you intended to retrieve multiple carts and add them all to the entity.cart collection.
In this code block (which is what is actually throwing the error:
entity.cart[0].nama_produk = cart.nama_produk;
entity.cart[0].jumlah = cart.jumlah;
entity.cart[0].harga = cart.harga;
entity.cart[0].subtotal = cart.subtotal;
entity.cart[0].notes = cart.notes;
The code is not clear. My best guess at what you are trying to do is to add the details of the cart you found to a collection of cart information on the entity object. I also assume that entity.cart is a List or something like that.
If my assumptions are correct, then the reason you are getting the reason you are getting that exception is the cart[0] doesn't exist until you add a cart object to that list (which is probably what you are trying to do here. A more readable (and less error prone) way to do this would be:
var entityCart = new Cart {
nama_produk = cart.nama_produk,
jumlah = cart.jumlah,
harga = cart.harga,
subtotal = cart.subtotal,
notes = cart.notes
};
entity.cart.Add(entityCart);
It could be that you are trying to add products to a cart... in that case, you should change your variable names a bit, but the rest is very similar.

ASP.net MVC/EF/C# add none related table records to query in controller

I am trying to add records from table position for positionName(s) to let user select a position for employee when editing.My last attempts is to add a navigation property like field in company model
public virtual ICollection<Position> Mpositions { get; set; }
But all I got so far is null ref exception or no element in viewModel with property "PositionName" ass per viewbag didn't bother using everybody keeps recommending to avoid it so not going to do so either.
public ActionResult Edit([Bind(Include = "CompanyID,CompanyName,EntityForm,Address,Dissolute,CreationDate,FiscaleYear,Description")] Company company)
{
var GlbUpdate = db.Companies.Include(c => c.Members).Include(p => p.Mpositions);
List<Company> mdlCompanies = new List<Company>();
foreach (var item in GlbUpdate)
{
if ((item.Mpositions==null) || (item.Mpositions.Count() == 0))
{
item.Mpositions = (ICollection<Position>)new SelectList(db.Positions.Except((IQueryable<Position>)db.Positions.Select(xk => xk.Members)), "PositionID", "PositionName");
}
mdlCompanies.Add(item);
//I tried first to edit the Mpositions property directly in gblUpdate
//item.Mpositions = (IEnumerable<Position>)db.Positions.Select(p => new SelectListItem { Value = p.PositionID.ToString(), Text = p.PositionName}) ;
//(ICollection<Position>)db.Positions.ToListAsync();
}
In the view I have this
List<SelectListItem> mPositionNames = new List<SelectListItem>();
#*#this yields no results if I try gettign it from the compani record itself it gives a logic error where all id match all positionNames impossible to select an item and only positions already registered are available on the dropDownlist*#
#{foreach (var item in Model.Mpositions)
{
mPositionNames.Add(new SelectListItem() { Text = item.PositionName, Value = item.PositionID.ToString(), Selected = (false) ? true : false });
#*#selected attribute set to false not an issue, no data to select from :p so far*#
}
}
#*#null exception(if i try to midify Mpositions directly in controler) here or empty list if modify it then put it with original query in a new list*#
<div class="SectionContainer R-sectionContainerData" id="MwrapperDataRight">
#Html.DropDownListFor(mpos => item.PositionID, (SelectList)Model.Mpositions)
</div>
All I want to do is pull the positions table to create a drop downList so users can change the position of an employee but since position has a 1>many relation with employee not companies it is not bound automatically by EF nor I seem to be able to use Include() to add it.
Your query for editing positions are complex. This query must edit person's info only. Using Edit action for formalizing position's edit are not correct.It's againts to Single Responsibility Principle. Use ViewComponents for this situation. Load positions separately from person info.
I found a suitable solution using a model that encapsulate the other entities then using Partialviews/RenderAction so each part handles one entity/operation.

Saving a reordered List item from an MVC View Model

I have a view model which binds to a 'TreasureHuntDetails' object, which contains a list of clues. Here's part of the data model for it.
public TreasureHuntDetails()
{
Clues = new List<Clue>();
}
[Key]
public int TreasureHuntId { get; set; }
public List<Clue> Clues { get; set; }
On the page, I have a table. A foreach loop iterates through the list of clues to add them to the table, e.g.
#for (int i = 0; i < Model.Clues.Count; i++)
The table elements inside the for loop are quite large, but here's an example of one of the table element columns:
<td>#Html.DisplayFor(m => Model.Clues[i].Location)</td>
All well and good so far. Then I'm using JQuery UI to allow the items of the table to be reordered using drag and drop, like this:
<script type="text/javascript">
$(document).ready(function()
{
$("#clueTable tbody").sortable().disableSelection();
});
</script>
All well and good, I can drag and drop the elements.
The problem is that I don't know how to save the new order of elements and save them back to the database.
The first thing I tried was simply passing the list of clues to a controller method, but I found that once the list of clues reached the controller method, it was always null.
For example:
#Url.Action("ViewCluePage", #Model.Clues)
Even if I send the whole #Model, list of clues within is always null. Removing the new list instantiation from the constructor of the data model didn't solve this problem.
Another thing I tried was wrapping the whole table into a HTML form, but still the list of clues remains null.
So basically, this question is really two questions:
1) Why is the list of clues always null after sending the model object to a controller.
2) How to save the new order of the list of items?
UPDATE: As per suggestion by #recursive, I see where I made an error when trying to submit the clue elements to the HTML form.
I used this outside the for loop which iterated over the clue elements:
#Html.HiddenFor(m => m.Clues)
I had to add the HiddenFor lines inside of the for loop (for each clue item), and for EACH property of the clue item, e.g.
#Html.HiddenFor(m => m.Clues[i].Id)
So that would be one step forward to be able to get the list items sent to the controller, but I think I still need code that will reflect the new order of the clue items when sent to the controller. Currently, on rearranging the order of the elements on screen using the JQuery sortable() method, this doesn't change the order of the elements as they are stored in the data model binded to the view (#Model.Clues).
1) As #resursive said in his comment, you need to have hidden elements on the page that map to properties in your Clue class.
2) As for persisting the order of clues, you'll need to add a column to your database that holds the position of each clue in the list and add the position property to your class. So your class would need to include
public int Position {get;set;}
which should pull from the database when the page is created. Then just before rendering the page, you should reorder the clue list based on the Position variable.
Edit: Use jquery's sortable attribute. Check out this thread for reference. In the stop drag event (or right before your submit), loop through each of your draggable objects and set the value of each of the hidden Position properties of your objects.
var positionIndex = 0;
$('.draggableObjectClass).each(function () {
$(this).find('input[id$=_Position]').val(positionIndex++);
});
but I think I still need code that will reflect the new order of the clue items when sent to the controller.
You won't, as you are now iterating over them in a for loop, they will be indexed in the order that you sent them to the view. Your order must already be maintained.
Taking advice from the answers posted here already, I came up with the following solution.
With already having this method in place to implement the drag and drop reordering of the UI elements,
$(document).ready(function()
{
$("#clueTable tbody").sortable().disableSelection();
});
I needed a way to be able read the in the new order of items and send it to the MVC controller. To do this I used the Razor #Html.AttributeEncode method to write the Id's of each item to a column on each row of the table, like this:
<td class="Ids" id="#Html.AttributeEncode(Model.Clues[i].Id)">#{var number = i + 1; #number}</td>
(This is wrapped around a for loop which iterates through the list of items.)
Then, I created the following Javascript function, which is invoked from a 'SaveNewOrder' button I placed above my table of elements (the user presses this once they have finished reordering the items on the table):
function getNewOrder()
{
var positions = new Array();
for (var i = 0; i < $('.Ids').length; i++)
{
positions[i] = $('.Ids')[i].id;
}
$.ajax(
{
type: "POST",
url: "#Url.Action("ReorderClues", "Clues")",
data:{ treasureHuntDetails: $("form").serialize(), ids: JSON.stringify(positions) }
contentType:'application/json'
}).done(function()
{
window.location.href = '#Url.Action("Clues", Model)';
}).
}
What this is does is reads the Id elements from each of the table items, and writes them into the array - so this array contains the NEW order of Id's. The data model containing the items doesn't change after reordering the table elements, hence why this was necessary.
It then uses a JQuery Ajax method to invoke a 'ReOrderClues' method on my 'Clues' MVC controller, passing a serialised version of the data model (containing a list of the clue items in the original order) and an array containing a list of the clue Id's in the new order. When the result is returned from the controller (.done), I invoke a controller which refreshes the page elements.
So rather than having to maintain a position value associated with each clue (which would involve significant refactoring elsewhere in the code), what I'm doing is swapping the contents of the clues around to reflect the new order, but keeping the Id's in the same position.
This is how I achieved that using an MVC Controller:
public ActionResult ReorderClues(TreasureHuntDetails treasureHuntDetails, int[] ids)
{
using (var db = new TreasureHuntDB())
{
var clues = treasureHuntDetails.Clues;
var newClues = NewOrderList(clues, ids);
// Save the changes of each clue
for (var i = 0; i < newClues.Count;i++ )
{
db.Entry(clues[i]).CurrentValues.SetValues(newClues[i]);
db.SaveChanges();
}
treasureHuntDetails.Clues = newClues;
TempData["Success"] = "Clues reordered";
}
return RedirectToAction("Clues", treasureHuntDetails);
}
public List<Clue> NewOrderList(List<Clue> clues, int[] ids)
{
var newClueOrder = new List<Clue>();
// For each ID in the given order
for (var i = 0; i < ids.Length; i++)
{
// Get the original clue that matches the given ID
var clue = clues.First(clue1 => clue1.Id == ids[i]);
var newClue = Clue.Clone(clue);
// Add the clue to the new list.
newClueOrder.Add(newClue);
// Retain the ID of the clue
newClueOrder[i].Id = clues[newClueOrder.Count - 1].Id;
}
return newClueOrder;
}
In the above code snippet, TreasureHuntDB is my Entity Framework database context.

LINQ to SharePoint 2010 getting error "All new entities within an object graph must be added/attached before changes are submitted."

I've been having a problem for some time, and I've exhausted all means of figuring this out for myself.
I have 2 lists in a MS Sharepoint 2010 environment that are holding personal physician data for a medical group...nothing special just mainly text fields and a few lookup choice fields.
I am trying to write a program that will migrate the data over from List A to List B. I am using LINQ to Sharepoint to accomplish this. Everything compiles just fine, but when it runs and hits the SubmitChanges() method, I get a runtime error that states:
"All new entities within an object graph must be added/attached before changes are submitted."
this issue must be outside of my realm of C# knowledge because I simply cannot find the solution for it. The problem is DEFINITELY stemming from the fact that some of the columns are of type "Lookup", because when I create a new "Physician" entity in my LINQ query, if I comment out the fields that deal with the lookup columns, everything runs perfectly.
With the lookup columns included, if I debug and hit breakpoints before the SubmitChanges() method, I can look at the new "Physician" entities created from the old list and the fields, including data from the lookup columns, looks good, the data is in there the way I want it to be, it just flakes out whenever it tries to actually update the new list with the new entities.
I have tried several methods of working around this error, all to no avail. In particular, I have tried created a brand new EntityList list and calling the Attach() method after each new "Physician" Entity is created, but to no avail, it just sends me around in a bunch of circles, chasing other errors such as "ID cannot be null", "Cannot insert entities that have been deleted" etc.,
I am no farther now than when I first got this error and any help that anyone can offer would certainly be appreciated.
Here is my code:
using (ProviderDataContext ctx = new ProviderDataContext("http://dev"))
{
SPSite sitecollection = new SPSite("http://dev");
SPWeb web = sitecollection.OpenWeb();
SPList theOldList = web.Lists.TryGetList("OldList_Physicians");
//Create new Physician entities.
foreach(SPListItem l in theOldList.Items)
{
PhysiciansItem p = new PhysiciansItem()
{
FirstName = (String)l["First Name"],
Title = (String)l["Last Name"],
MiddleInitial = (String)l["Middle Init"],
ProviderNumber = Convert.ToInt32(l["Provider No"]),
Gender = ConvertGender(l),
UndergraduateSchool =(String)l["UG_School"],
MedicalSchool = (String)l["Med_School"],
Residency = (String)l["Residency"],
Fellowship = (String)l["Fellowship"],
Internship = (String)l["Internship"],
PhysicianType = ConvertToPhysiciantype(l),
Specialty = ConvertSpecialties(l),
InsurancesAccepted = ConvertInsurance(l),
};
ctx.Physicians.InsertOnSubmit(p);
}
ctx.SubmitChanges(); //this is where it flakes out
}
}
//Theses are conversion functions that I wrote to convert the data from the old list to the new lookup columns.
private Gender ConvertGender(SPListItem l)
{
Gender g = new Gender();
if ((String)l["Sex"] == "M")
{
g = Gender.M;
}
else g = Gender.F;
return g;
}
//Process and convert the 'Physician Type', namely the distinction between MD (Medical Doctor) and
//DO (Doctor of Osteopathic Medicine). State Regualtions require this information to be attached
//to a physician's profile.
private ProviderTypesItem ConvertToPhysiciantype(SPListItem l)
{
ProviderTypesItem p = new ProviderTypesItem();
p.Title = (String)l["Provider_Title:Title"];
p.Intials = (String)l["Provider_Title"];
return p;
}
//Process and convert current Specialty and SubSpecialty data into the single multi-choice lookup column
private EntitySet<Item> ConvertSpecialties(SPListItem l)
{
EntitySet<Item> theEntityList = new EntitySet<Item>();
Item i = new Item();
i.Title = (String)l["Provider Specialty"];
theEntityList.Add(i);
if ((String)l["Provider SubSpecialty"] != null)
{
Item theSubSpecialty = new Item();
theSubSpecialty.Title = (String)l["Provider SubSpecialty"];
theEntityList.Add(theSubSpecialty);
}
return theEntityList;
}
//Process and add insurance accepted.
//Note this is a conversion from 3 boolean columns in the SP Environment to a multi-select enabled checkbox
//list.
private EntitySet<Item> ConvertInsurance(SPListItem l)
{
EntitySet<Item> theEntityList = new EntitySet<Item>();
if ((bool)l["TennCare"] == true)
{
Item TenncareItem = new Item();
TenncareItem.Title = "TennCare";
theEntityList.Add(TenncareItem);
}
if ((bool)l["Medicare"] == true)
{
Item MedicareItem = new Item();
MedicareItem.Title = "Medicare";
theEntityList.Add(MedicareItem);
}
if ((bool)l["Commercial"] == true)
{
Item CommercialItem = new Item();
CommercialItem.Title = "Commercial";
theEntityList.Add(CommercialItem);
}
return theEntityList;
}
}
So this may not be the answer you're looking for, but it's what's worked for me in the past. I've found that updating lookup fields using Linq to Sharepoint to be quite frustrating. It frequently doesn't work, or doesn't work efficiently (forcing me to query an item by ID just to set the lookup value).
You can set up the entity so that it has an int property for the lookup id (for each lookup field) and a string property for the lookup value. If, when you generate the entities using SPMetal, you don't generate the list that is being looked up then it will do this on it's own. What I like to do is (using your entity as an example)
Generate the entity for just that one list (Physicians) in some temporary folder
Pull out the properties for lookup id & value (there will also be private backing fields that need to come along for the ride too) for each of the lookups (or the ones that I'm interested in)
Create a partial class file for Physicians in my actual project file, so that regenerating the entire SPMetal file normally (without restricting to just that list) doesn't overwrite changes
Paste the lookup id & value properties in this partial Physicians class.
Now you will have 3 properties for each lookup field. For example, for PhysicianType there will be:
PhysicianType, which is the one that is currently there. This is great when querying data, as you can perform joins and such very easily.
PhysicianTypeId which can be occasionally useful for queries if you only need ID as it makes it a bit simpler, but mostly I use it whenever setting the value. To set a lookup field you only need to set the ID. This is easy, and has a good track record of actually working (correctly) in my experiences.
PhysicianTypeValue which could be useful when performing queries if you just need the lookup value, as a string (meaning it will be the raw value, rather than something which is already parsed if it's a multivalued field, or a user field, etc. Sometimes I'd rather parse it myself, or maybe just see what the underlying value is when doing development. Even if you don't use it and use the first property, I often bring it along for the ride since I'm already doing most of the work to bring the PhysicianTypeId field over.
It seems a bit hacky, and contrary to the general design of linq-to-SharePoint. I agree, but it also has the advantage of actually working, and not actually being all that hard (once you get the rhythm of it down and learn what exactly needs to be copied over to move the properties from one file to another).

How do I persist checked rows on a grid that is populated from Cache instead of Session?

I get a large list of data to populate into a jqGrid on my clientside.
List<MyData> lotsOfRecords = getData();
Which I then store in cache, since a lot of people will be using it:
Cache["SharedData"].Add(lotsOfRecords);
This grid allows users to check records for processing. I want to persist which records are checked as a user sorts, filters, pages, etc.
My first thought was to add a property bool Selected { get; set; } to the MyData object, and toggle it whenever someone checks a field. Obviously, that won't work since this is a shared cache. I don't want Joe User checking things that Bill User didn't want checked.
Next idea was to store a Dictionary<int, bool> in session, that maps the id of a record to the checked status. This wasn't bad, but since there is no easy way to combine objects in .NET, I don't see a clean way to send that down to my grid without a clunky anonymous object:
return lotsOfRecords.Select(record => {
record.Id,
record.Name,
...
myDictionary[record.Id] // true/false for checked
};
That would be a solution, but I'm hoping there is a cleaner design pattern considering I have a lot of fields in my object and use it in a similar way across a few pages. Any suggestions are welcome. Thanks in advance!
Btw, my current environment is ASP.NET MVC 3 with jQuery/UI and jqGrid.
You may be caching the list, but the selections will need to be user-specific. I would suggest building a list of the selected indices each time the page is posted back, and store the list session.
Here's a function that we're using to remember selections as the user pages through results:
/// <summary>
/// Iterates through items in the grid and updates the selected vendor
/// list with any selections or deselections on the current page
/// </summary>
private void UpdateSelectedItems()
{
var selectedVendors = new List<int>();
foreach (GridItem Item in grdVendors.Items)
{
if (Item is GridDataItem)
{
int VendorID = (int)((GridDataItem)Item).GetDataKeyValue("SupplierID");
if (Item.Selected)
{
if (!selectedVendors.Contains(VendorID))
selectedVendors.Add(VendorID);
continue;
}
selectedVendors.Remove(VendorID);
}
}
}
I'm not sure why you think "combining" objects is tough. You can simply do this:
public class SelectableDataObject
{
public SelectableDataObject(MyDataObject obj)
{
this.DataObject = obj;
}
public MyDataObject DataObject { get; private set; }
public bool Selected {get;set;}
}
Then you can just do this:
return lotsOfRecords.Select(record => {
return new SelectableDataObject(record){Selected = myDictionary.ContainsKey(record.Id)}
};
Alternatively, in your view model you can have the list of objects and the dictionary as two separate properties, and when you iterate the list of objects in your view to populate your grid, you can check the dictionary if the Id exists and check/uncheck based on that. This way is a bit more clunky, but should work.
Either way, I think your dictionary idea is perfectly fine, the only thing I'd do different is just store the Id's of the ones that are selected, that way you only store a subset for each user.

Categories