Collection was modified when adding to collection - c#

Getting the "Collection was modified" exception when attempting to add to a collection
public void UpdateLinks(EventViewModel form)
{
var selectedIds = form.Links.Select(r => r.ResourceTypeID).ToList();
var assignedIds = form.Event.Links.Select(r => r.ResourceTypeID).ToList();
foreach (var resource in form.Links)
{
resource.EventID = form.Event.ID;
if (!assignedIds.Contains(resource.ResourceTypeID))
form.Event.Links.Add(resource);
}
foreach (var resource in form.Event.Links.ToList())
{
if (!selectedIds.Contains(resource.ResourceTypeID))
form.Event.Links.Remove(resource);
}
}
The problem is specifically with the "Add" method. If I comment that part out, no exception is thrown. It's important to note that I've already tried re-writing the foreach as a for loop and adding "ToList()" to form.Links. The same exception is thrown in all cases. I use this exact pattern on other parts of the site without issue which is why this is so frustrating. This also works on "Create". The problem only affects the "Edit" action.
Other relevant code:
[HttpPost]
public ActionResult Edit(EventViewModel form, HttpPostedFileBase[] eventFiles)
{
if (ModelState.IsValid)
{
eventsService.UpdateEvent(form.Event);
eventsService.UpdateManufacturerTags(form);
eventsService.UpdateFiles(form, eventFiles);
eventsService.UpdateLinks(form);
eventsService.Save();
return RedirectToAction("Details", new { id = form.Event.ID });
}
return View(form);
}
public class EventViewModel : ContentLeftViewModel
{
public Event Event { get; set; }
public string[] SelectedManufacturers { get; set; }
public MultiSelectList Manufacturers { get; set; }
public IList<EventResource> Files { get; set; }
public IList<EventResource> Links { get; set; }
public EventViewModel()
{
SelectedManufacturers = new string[0];
Files = new List<EventResource>();
Links = new List<EventResource>();
}
}
public class Event
{
[Key]
public int ID { get; set; }
[Required]
public string Title { get; set; }
[Required]
[DisplayName("Start Time")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:M/d/yyyy h:mm tt}")]
public DateTime? StartTime { get; set; }
[Required]
[DisplayName("End Time")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:M/d/yyyy h:mm tt}")]
public DateTime? EndTime { get; set; }
public string Venue { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[AllowHtml]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[DisplayName("Registration Link")]
public string RegistrationUrl { get; set; }
public virtual IList<Manufacturer> Manufacturers { get; set; }
public virtual IList<EventResource> Files { get; set; }
public virtual IList<EventResource> Links { get; set; }
//public IEnumerable<EventResource> Resources
//{
// get { return Files.Concat(Links); }
//}
public string StartDate
{
get { return StartTime.Value.ToShortDateString(); }
}
public string StartTimeOnly
{
get { return StartTime.Value.ToShortTimeString(); }
}
public string EndDate
{
get { return EndTime.Value.ToShortDateString(); }
}
public string EndTimeOnly
{
get { return EndTime.Value.ToShortTimeString(); }
}
public Event()
{
Manufacturers = new List<Manufacturer>();
Files = new List<EventResource>();
Links = new List<EventResource>();
}
}
public class EventResource
{
[Key, Column(Order = 0)]
public int EventID { get; set; }
[Key, Column(Order = 1)]
public int ResourceTypeID { get; set; }
[Key, Column(Order = 2)]
public string Path { get; set; }
public virtual Event Event { get; set; }
public virtual ResourceType Type { get; set; }
}
UPDATE
Some more info: Adding to the collection at all... even outside of a loop throws the same error. Does that give anyone an idea?

Try this instead:
var lsEvents = form.Event.Links.ToList();
foreach (var resource in form.Links)
{
resource.EventID = form.Event.ID;
if (!assignedIds.Contains(resource.ResourceTypeID))
lsEvents.Add(resource);
}
foreach (var resource in form.Event.Links)
{
if (!selectedIds.Contains(resource.ResourceTypeID))
lsEvents.Remove(resource);
}
and Use lsEvents according to the requirement. This will fix your issue.

You can use LINQ to filter out entries before adding them.
public void UpdateLinks(EventViewModel form)
{
var selectedIds = form.Links.Select(r => r.ResourceTypeID).ToArray();
var assignedIds = form.Event.Links.Select(r => r.ResourceTypeID).ToArray();
foreach (var resource in form.Links
.Where(r=> !assignedIds.Contain(r.ResourceTypeID)).ToArray())
{
resource.EventID = form.Event.ID;
form.Event.Links.Add(resource);
}
foreach (var resource in form.Event.Links
.Where(r=> !selectedIds.Contain(r.ResourceTypeID)).ToArray())
{
form.Event.Links.Remove(resource);
}
}
In both methods, we are filtering resources before enumerating and adding. You can not enumerate and add at the same time.

You can't modify a collection you are enumerating (e.g., for each).
You need to loop once to get the items you want to add and/or remove, then add or remove all of them in a second loop outside the first.
E.g.:
Dim coll = New List(of String)({"1", "2", "3", "4", "6"})
dim coll2 = New List(of String)({"5", "8", "9", "2"})
Dim removeItems as new list(of String)()
For Each item in coll
For Each item2 in coll2
If item2 = item
removeItems.Add(item)
end if
Next item2
Next item
' remove the items gathered
For each itemToRemove in removeItems
coll.Remove(itemToRemove)
Next itemToRemove
It can be done a better way, but this shows the gist of the error. You can't change the collection you are looping over.

Not sure exactly what was responsible for solving the issue, but after upgrading to Visual Studio 2013 Express (from 2010 Professional), and installing ASP.Net 4.5/IIS8 with it, even though this application continues to target ASP.Net 4.0, I am no longer experiencing the issue and the code used in the original post works as is.
Perhaps this was caused by a certain build of the ASP.Net framework or an older version of IIS?

Related

Automapper executes without error, but no data being copied from source to destination

I have a class like this
public class ListOfBMTTeamMapping
{
public class TeamMapping
{
public List<TeamMappings> results { get; set; }
}
public class TeamMappings
{
public int id { get; set; }
public string areaPath { get; set; }
public string agileReleaseTrainName { get; set; }
public string deliveryTeamName { get; set; }
public string keyedInTeamCode { get; set; }
public string deliveryTeamId { get; set; }
public bool isDeleted { get; set; }
public string modified { get; set; }
public string modifiedBy { get; set; }
}
}
And here is my model class to which I need the above API class to get copied
public class JsonBmtAdoMapping
{
public int? Id { get; set; }
public string AreaPath { get; set; }
public string AgileReleaseTrainName { get; set; }
public string DeliveryTeamName { get; set; }
public string KeyedInTeamCode { get; set; }
public string DeliveryTeamId { get; set; }
public string IsDeleted { get; set; }
public DateTime? Modified { get; set; }
public string ModifiedBy { get; set; }
}
So here is my code I tried
var format = "dd/MM/yyyy";
var dateTimeConverter = new IsoDateTimeConverter { DateTimeFormat = format };
ListOfBMTTeamMapping.TeamMapping Results = new ListOfBMTTeamMapping.TeamMapping();
Results = JsonConvert.DeserializeObject<ListOfBMTTeamMapping.TeamMapping>(responseBody);
List<JsonBmtAdoMapping> jM = new List<JsonBmtAdoMapping>();
jM = _mapper.Map<ListOfBMTTeamMapping.TeamMapping,List<JsonBmtAdoMapping>>(Results);
int n = 10;
And here is my automapper profile
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>();
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
But when the code executes, Ofcourse I am getting the data in results variable without any trouble
But when the mapper code fires, it execute the line without any error, but no data being copied from source to my model class which is the destination
jM.count is always 0 when Results hold 124 rows of data
What I did wrong
Your mapping from TeamMapping to List<JsonBmtAdoMapping> can't be done out of the box by AutoMapper, because your source is an object with a property that contains the list and the destination is a list on itself.
So you have to tell him, how this conversion from a single object to a list can be done. Due to the fact, that you already have a mapping for each individual item, we can use that recursively within our mapping method.
By using this mapping, it should work:
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => src.results.Select(context.Mapper.Map<JsonBmtAdoMapping>).ToList());
Update
Cause a mapper is already defined for the individual items and lists are handled automatically by AutoMapper we can even make it shorter (thanks for Lucian for the hint in the comments):
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => context.Mapper.Map<List<JsonBmtAdoMapping>>(src.results));

merging two multi level class objects with element update c#

I have two objects (A,B) of same class type (PPLWebOperatorGridList). I need update the A.OldValue with B.Value.
I have tried by adding the guid property and update it in the constructor as shown below. But these object list may repeat same value:
public PPLWebOperatorGridList()
{
this.guid = this.FieldName+this.TagName+
this.Length+this.Encoder+this.Value;
}
public string guid { get; set; }
I have tried as below. I know there are bugs in it but consider the idea in it.
private List<PPLWebOperatorGridList> UpddateOldValues(List<PPLWebOperatorGridList> customeTlvList, List<PPLWebOperatorGridList> customeTlvList2)
{
foreach (var list in customeTlvList)
{
foreach (var list1 in customeTlvList2)
{
if (list.guid == list1.guid)
{
list.OldValue = list1.Value;
if (list.children.Count > 0)
UpddateOldValues(list.children.ToList(), list1.children.ToList());
}
}
}
return customeTlvList;
}
The guid property may be same for some in the list.
class PPLWebOperatorGridList
{
public bool expanded { get; set; }
public string FieldName { get; set; }
public string TagName { get; set; }
public string Length { get; set; }
public string Encoder { get; set; }
public string Value { get; set; }
public List<PPLWebOperatorGridList> children { get; set; }
public string OldValue { get; set; }
}
I need to loop through based on index and update the A.OldValue with B.Value. I am not very familiar with linq, so please suggest a solution.

How to instantiate and use classes with a list of classes?

I have a dataset structure such that a Project contains multiple Jobs, and each Job contains multiple WorkItems. I’ve created the following three classes:
public class WorkItemForRollup
{
public Guid JobID { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public DateTime ScheduledDate { get; set; }
}
public class JobForRollup
{
public Guid JobID { get; set; }
public string JobName { get; set; }
public decimal Price { get; set; }
public DateTime ScheduledStart { get; set; }
public List<WorkItemForRollup> WorkItems { get; set; }
}
public class ProjectRollup
{
public decimal Total { get; set; }
public List<JobForRollup> Jobs { get; set; }
}
Can someone please tell me the syntax for instantiating this stuff and subsequently assigning values to the class members? Here’s what I’ve been trying to get to work:
ProjectRollup projectRollup = new ProjectRollup() { Jobs = new List<JobForRollup>() };
JobForRollup jobsForRollup = new JobForRollup() { WorkItems = new List<WorkItemForRollup>() };
I can then do a projectRollup.Jobs.Add(job); where ‘job’ is a JobForRollup entity,
but I can’t figure out how to add my WorkItems into each Job.
Can you please help, or show me another way that works? Thank you!
To have your list initialized you can write your classes in this way
public class WorkItemForRollup
{
public Guid JobID { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public DateTime ScheduledDate { get; set; }
}
public class JobForRollup
{
public Guid JobID { get; set; }
public string JobName { get; set; }
public decimal Price { get; set; }
public DateTime ScheduledStart { get; set; }
public List<WorkItemForRollup> WorkItems { get; set; } = new List<WorkItemForRollup();
}
public class ProjectRollup
{
public decimal Total { get; set; }
public List<JobForRollup> Jobs { get; set; } = new List<JobForRollup>();
}
Now you have all your List property automatically initialized every time you build a new instance of these classes. This syntax is called Initialize Auto-Implemented properties and it is available starting from C# 6.0
Now, the List properties that are supposed to receive items of the appropriate type, are correctly initialized, but, of course, they are empty.
To add an item to the WorkItems inside the JobForRollup class you need to create an instance of a WorkItemForRollup and add it to the JobForRollup instance
ProjectRollup projectRollup = new ProjectRollup();
JobForRollup job = new JobForRollup();
job.WorkItems.Add(Get_A_WorkItem_Instance_From_Your_Code());
projectRollup.Jobs.Add(job);
// As an example
private WorkItemForRollup Get_A_WorkItem_Instance_From_Your_Code()
{
WorkItemForRollup item = new WorkItemForRollup
{
Description="Description",
Price = 42.42m,
ScheduledDate = DateTime.Now.AddDays(30);
};
return item;
}
Actually, This is not a good approach for instantiating this n-level nested types, but the below code is a sample:
ProjectRollup projectRollup = new ProjectRollup()
{
Jobs = new List<JobForRollup>()
{
new JobForRollup()
{
JobID = Guid.NewGuid(),
JobName = "job1",
Price = 434,
ScheduledStart = DateTime.Now,
WorkItems = new List<WorkItemForRollup>()
{
new WorkItemForRollup()
{
Description="item1",
Price = 34,
ScheduledDate = DateTime.Now
}
}
}
}
};
You should attach each nested objects and collection together.
describe your problem and explain why you are not using ORM's such as EntityFramework, nhibernate, etc?
Initializing nested Collections is always a bit of a annoying thing. But as you already have properties, that is simple enough to do. Just stop using the auto-implement properties and write your own one that looks like this:
private List<JobForRollup> _Jobs;
public List<JobForRollup> Jobs {
get{
if (_Jobs == null)
_Jobs = new List<JobForRollup>();
return _Jobs;
}
set{
_Jobs=value;
}
}
The biggest danger with writing your own Property code is accidentally having the backing field accessed by other class code. But adding a underscore as prefix has prooven reliable enough to be used in all example code of Microsoft.

DropDownListFor Properties of Collection in ViewModel

I am new to ASP.NET MVC and was/am not sure exactly how to word my problem. I have to create a page where the user can add a list of multiple collateral items to a loan application. Each "row" for each collateral items needs several Dropdownlist to select the type of collateral, its class etc. The page is based on a ViewModel:
public class CollateralViewModel
{
public Guid LoanApplicationId { get; set; }
public IEnumerable<CollateralRowViewModel> Collateral { get; set; }
}
The IEnumerable Collateral gets the following:
public class CollateralRowViewModel
{
public Guid Id { get; set; }
public Guid LoanApplicationId { get; set; }
public IEnumerable<SelectListItem> CollateralClass { get; set; }
public IEnumerable<SelectListItem> CollateralType { get; set; }
public Guid SelectedCollateralType { get; set; }
public Guid SelectedCollateralClass { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public decimal? MarketValue { get; set; }
public decimal? PriorLiens { get; set; }
public decimal? AdvanceRate { get; set; }
public string GrantorFirstName { get; set; }
public string GrantorMiddleName { get; set; }
public string GrantorLastName { get; set; }
}
My Controller looks like this:
public async Task<ActionResult> Create(CollateralViewModel collateralViewModel)
{
var collateralServiceProxy = base.ServiceProvider.CollateralServiceProxy;
var collateralTypes = await GetCollateralTypesByClass(Guid.NewGuid());
var selectedCollateral = collateralViewModel.Collateral.Select(collateral => new Collateral()
{
Id = collateral.Id,
LoanApplicationId = collateral.LoanApplicationId,
CollateralTypeId = collateral.SelectedCollateralType,
Description = collateral.Description,
GrantorFirstName = collateral.GrantorFirstName,
GrantorMiddleName = collateral.GrantorMiddleName,
GrantorLastName = collateral.GrantorLastName,
PriorLiens = collateral.PriorLiens,
MarketValue = collateral.MarketValue
});
foreach (var collateral in selectedCollateral)
{
await collateralServiceProxy.PutCollateralAsync(collateral);
}
return View(collateralViewModel);
}
private async Task<IEnumerable<SelectListItem>> GetCollateralClasses()
{
var collateralServiceProxy = base.ServiceProvider.CollateralServiceProxy;
var collateralClasses = await collateralServiceProxy.GetAllCollateralClassesAsync();
if (collateralClasses == null)
{
return new List<SelectListItem>();
}
return collateralClasses.ToSelectList();
}
private async Task<IEnumerable<SelectListItem>> GetCollateralTypesByClass(Guid collateralClassId)
{
var allCollateralTypes = await GetAllCollateralTypes();
var selectedCollateralTypes = allCollateralTypes.Where(collateralType => Guid.Parse(collateralType.Value).Equals(collateralClassId));
return selectedCollateralTypes;
}
When I try to use #Html.DropDownListFor(model=>model.Collateral.CollateralClasses) I cannot because CollateralClasses is unavailable. I type "(model.Collateral." and the properties aren't there. What am I doing wrong here?
Any help is greatly appreciated!!
Please check you dropdownlistfor syntex. I think that may be the issue (because you have not specified the error).Please check the syntex
#Html.DropDownListFor(m => m.ContribType,
new SelectList(Model.ContribTypeOptions,
"ContribId", "Value",
Model.ContribTypeOptions.First().ContribId))
so you may try it like this. In your first place "valueItRepresent", it should be the property for which the dropdown is for
#Html.DropDownListFor(model => model.valueItRepresent, model.Collateral.FirstOrDefault().CollateralClasses‌ as SelectList, "Select")
The issue is your dropdownlist not populated when rendering the UI. You have to properly populate dropdownlist before render data.This can be done in several ways, but I would recommend reading below article.
This article provide solution for your question
http://odetocode.com/blogs/scott/archive/2013/03/11/dropdownlistfor-with-asp-net-mvc.aspx
Hope this helps.

Updating List<T> in DbContext

I have a Model like this
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<string> SolvedBy { get; set; }
}
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
}
and then Controller like this. But I cannot update the List "SolvedBy", the next time I step through with the debugger, the list is still empty.
[HttpPost]
public string Index(string flag = "", int id=0)
{
Challenge challenge = db.Challenges.Find(id);
if (flag == challenge.Flag)
{
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<string>();
}
chall.SolvedBy.Add(User.Identity.Name);
db.Entry(chall).State = EntityState.Modified;
db.SaveChanges();
//congrats, you solved the puzzle
return "got it";
}
else
{
return "fail";
}
}
is there any way around it to make a list of strings kept in the database?
EF don't know how to store an array in database table so it just ignore it. You can create another table/entity or use XML/JSON to store the list. You can serialize the list before saving and deserialize it after loading from database
A List<T> in a model would normally map to a second table, but in your DbContext you only have a single table. Try adding a second table.
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
public DbSet<Solution> Solutions {get; set;}
}
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<Solution> SolvedBy { get; set; }
}
public class Solution
{
public int ID { get; set; }
public string Name { get; set; }
}
Then your controller can use code along the lines of...
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<Solution>();
}
chall.SolvedBy.Add(new Solution {Name=User.Identity.Name});
None of the above has been tested and I may have made some mistakes there, but the general principle I want to illustrate is the fact that you need another table. The List<T> represents a JOIN in SQL.

Categories

Resources