Looping through (IEnumerable) results and updating, but nothing updated? - c#

I am looping thru parent records ("europe") and updating its <Ienumerable> field called "childPublication" with its child records. But the childPublication is null after the loop and assignment?
Here's my code:
foreach (var e in europe)
{
string child = e.HasChild ?? "";
if (child.Contains("True"))
{
IEnumerable<Publication> eChildrens = children.OfType<Publication>()
.Where(ep => ep.ParentID.Equals(e.PublicationId));
if (eChildrens.Count() > 0)
{
e.ChildPublication = eChildrens;
}
}
}
member.EuropeMiddleEastAfricaPublication = europe;
public class Publication
{
public int PublicationId { get; set; }
public int ContentTypeId { get; set; }
public string PublicationName { get; set; }
public string PublicationFullName { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public string URL { get; set; }
public string CountryId { get; set; }
public string LanguageId { get; set; }
public string Active { get; set; }
public string Subscription { get; set; }
public string ClientOnly { get; set; }
public string PrintVersion { get; set; }
public string EmailVersion { get; set; }
public string RegisteredforPrint { get; set; }
public string RegisteredforEmail { get; set; }
public int ParentID { get; set; }
public string HasChild { get; set; }
public IEnumerable<Publication> ChildPublication { get; set; }
}

First, you have eChildren = children, so I'm assuming children is passed in somewhere?
I would probably code it something like:
foreach (var e in europe)
{
// .Net 4.0 use: string.IsNullOrWhiteSpace()
if (!string.IsNullOrEmpty(e.HadChild)
// I prefer IndexOf which allows Culture and IgnoreCase
&& e.HasChild.IndexOf("True", StringComparison.CurrentCultureIgnoreCase))
{
IEnumerable<Publication> eChildrens =
children.OfType<Publication>()
.Where(ep => ep.ParentID.Equals(e.PublicationId))
.ToList(); //Force the IEnumeration to Enumerate.
if (eChildrens.Count() > 0)
{
e.ChildPublication = eChildrens;
}
}
}

You should debug your program and verify that you are actually getting inside your if and setting the property. If you aren't, then it will absolutely be null. But note that you are doing something dangerous anyway by closing over the loop variable.
IEnumerable<Publication> eChildrens =
children.OfType<Publication>().Where(ep =>
ep.ParentID.Equals(e.PublicationId));
if (eChildrens.Count() > 0)
{
e.ChildPublication = eChildrens;
}
eChildrens is a lazily evaluated query and it is capturing the loop variable e. When you get outside the query and try to use the results, unless you have weird expectations, you code is not going to do what you want. In a closure, it is the variable that is captured, so your query will always be looking at the same var e when you get outside of the loop. You're going to have a lot of objects looking at the wrong ChildPublication sequences.
To avoid this issue, either create a local temporary variable inside the loop and close over that
var temp = e; // local temporary variable, used below
IEnumerable<Publication> eChildrens =
children.OfType<Publication>().Where(ep =>
ep.ParentID.Equals(temp.PublicationId));
if (eChildrens.Count() > 0)
{
e.ChildPublication = eChildrens;
}
Or alternately force evaluation of the query by invoking a method such as ToList();
IEnumerable<Publication> eChildrens =
children.OfType<Publication>().Where(ep =>
ep.ParentID.Equals(e.PublicationId)).ToList();
if (eChildrens.Count() > 0)
{
e.ChildPublication = eChildrens;
}
For more on this topic, please read Eric Lippert's blog entry.

Here is the problem. IEnumerable users an iterator run against your data source. Iterators return a new object, not a reference to the original object.
So, you are making changes on a copy of your original data, not your actual data. If you want to modify your original data, you need to pass a reference to it and work with that reference.

Have you tried using a List<Publication> instead or IEnumerable<Publication>. I always have much more sucess with it for simple collection handling. IEnumerable often times wants an Enumerator defined which is a little more overhead.

Related

How to add items to existing list of objects?

I have three classes:
public class M2ArticleMain
{
public int Id { get; set; }
public List<M2ArticleAttributeWeb> Attribut_Web { get; set; }
}
public class M2ArticleAttributeWeb
{
public int Web_Id { get; set; }
public M2ArticleTmpMainSkus Variants { get; set; }
}
public class M2ArticleTmpMainSkus
{
public DateTime TimeAdded { get; set; }
public List<string> Skus { get; set; }
}
And I have two Lists in my code like this:
List<M2ArticleMain> data = new List<M2ArticleMain>();
List<M2ArticleAttributeWeb> attb = new List<M2ArticleAttributeWeb>();
In some part of my code firstly I (from foreach loop) add data to attb list where I add only only some data (because I don't have all data at this point), like this:
...
attb.Add(new M2ArticleAttributeWeb
{
Web_id = item.Id, //(item is from foreach loop)
Variants = null //this is **importat**, I left null for later to add it
});
Next, after I fill attb, I add all this to data list:
...
data.Add(new M2ArticleMain
{
Id = item.Id_Pk, //this is also from foreach loop,
Attribut_Web = attb //now in this part I have only data for Web_id and not Variants
}
Now my question is How to Add items later to data list to object Variants?
Something like this:
data.AddRange( "how to point to Variants" = some data);
The M2ArticleAttributeWeb type holding your Variants property is the member of a collection. That is, there are potentially many of them. You can reference an individual Variants property like this:
data[0].Attribut_Web[0].Variants
But you need to know which items you want to add map to which data and Attribut_Web indexes/objects in order to assign them properly. That probably means another loop, or even a nested loop. That is, you can see all of your Variants properties in a loop like this:
foreach(var main in data)
{
foreach(var attrw in main)
{
var v = attrw.Variants;
// do something with v
Console.WriteLine(v);
// **OR**
attrw.Variants = // assign some object
}
}
It's also much better practice to create your collection properties with the object, and then give them private set attributes:
public class M2ArticleMain
{
public int Id { get; set; }
public List<M2ArticleAttributeWeb> Attribut_Web { get; private set; } = new List<M2ArticleAttributeWeb>();
}
public class M2ArticleAttributeWeb
{
public int Web_Id { get; set; }
public M2ArticleTmpMainSkus Variants { get; set; }
}
public class M2ArticleTmpMainSkus
{
public DateTime TimeAdded { get; set; }
public List<string> Skus { get; private set; } = new List<string>();
}
Now instead of assigning Attribut_Web = attb, you would need to .Add() to the existing List.

Separate Model with List

I want to return a list of links to a web page when it loads. Right now I have a model called SsoLink.cs bound to the page. I would like to return a list, so I have created another model called SsoLinks.cs that has a List. In my helper function, I keep getting "object not set to an instance of an object".
SsoLink.cs
public class SsoLink
{
public enum TypesOfLinks
{
[Display(Name="Please Select a Type")]
Types,
Collaboration,
[Display(Name="Backups & Storage")]
Backups_Storage,
Development,
[Display(Name="Cloud Services")]
Cloud_Services,
[Display(Name="Human Resources")]
Human_Resources,
Analytics
}
public string Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Owner { get; set; }
public string OwnerEmail { get; set; }
public string LinkDescription { get; set; }
public TypesOfLinks LinkType { get; set; }
}
SsoLinks.cs
public class SsoLinks
{
public List<SsoLink> Links {get; set;}
}
GetLinksHelper.cs
public partial class SsoLinkHelper
{
public static SsoLinks GetLinks()
{
var ssoList = new SsoLinks();
try
{
//search the index for all sso entries
var searchResponse = _client.Search<SsoLink>(s => s
.Index(_ssoLinkIndex)
.Size(500)
.Query(q => q
.MatchAll()
)
);
if (searchResponse.Documents.Count == 0)
{
return ssoList;
}
ssoList.Links.AddRange(searchResponse.Hits.Select(hit => new SsoLink() {Id = hit.Source.Id, Name = hit.Source.Name, Url = hit.Source.Url, Owner = hit.Source.Owner}));
return ssoList;
}
catch (Exception e)
{
Log.Error(e, "Web.Helpers.SsoLinkHelper.GetLinks");
return ssoList;
}
}
}
While debugging, It is failing at SsoLinks.Links.AddRange(etc). How can I add a new SsoLink to the ssoList for every item found in my query?
Edit: Here is a screenshot of the error while debugging.
The null reference exception looks like it comes from ssoList.Links being null when calling AddRange on it, so it needs to be initialized to a new instance of List<SsoLink> before calling AddRange().
Russ's answer led me down the right path, I ended up just needing to change my view to:
#model List<SharedModels.Models.SsoLink>
rather than
#model SharedModels.Models.SsoLink
and do away with the SsoLinks model.

New class selected by LINQ query, how to transfer to another model?

I'm really not understanding this as I've only dabbled in MVC and C#. I apologize if my terminology is wrong or confusing, I will do my best to answer questions. I have a couple models like so:
public class DataSharingModels
{
public string ReferenceID { get; set; }
public NBTC NBTCGroup { get; set; }
public Contractors ContractorsGroup { get; set; }
public Coordinators CoordinatorsGroup { get; set; }
public NGO NGOGroup { get; set; }
public Public PublicGroup { get; set; }
public SelectList FA_RA_List { get; set; }
}
public class NBTC
{
public String NBTC_FA_Centroid { get; set; }
public String NBTC_FA_Bound { get; set; }
public String NBTC_RA_Centroid { get; set; }
//more properties...
}
The DataSharingModels class contains the public NBTC NBTCGroup property. It is not public List<NBTC> NBTCGroup because there will only be one produced per instance of the controller being hit.
Now in my controller, I have a LINQ statement that selects a new NBTC class:
var nbtcVals = (from ds in db.SharingPermissions
where ds.FocalRefID.ToString() == ReferenceID
&& ds.ShareGroup == "NBTC"
select new NBTC
{
NBTC_FA_Centroid = ds.CIP_FA_Centroid,
NBTC_FA_Bound = ds.CIP_FA_Boundary,
NBTC_RA_Centroid = ds.CIP_RA_Centroid,
//more properties...
});
Where I'm going wrong is I would like to add that to my DataSharingModels model. I thought the nbtcVals type would be NBTC, but it's IQueryable<##.Models.NBTC>. I understand I could do this, but it seems redundant:
DataSharingModels dsm = new DataSharingModels();
if (nbtcVals.Any())
{
foreach (var i in nbtcVals)
{
dsm.NBTCGroup.NBTC_FA_Centroid = i.NBTC_FA_Centroid;
dsm.NBTCGroup.NBTC_FA_Boundary = i.NBTC_FA_Bound;
dsm.NBTCGroup.NBTC_RA_Centroid = i.NBTC_RA_Centroid;
//more properties...
}
}
What is a more direct way to do this? There must be one. I supposed I could also return an anonymous type in the LINQ query and then assign each property in the foreach like dsm.NBTCGroup.NBTC_RA_Centroid = i.NBTC_RA_Centroid but that seems the same as the other way.
var nbtcgroup = (from ds in db.SharingPermissions
where ds.FocalRefID.ToString() == ReferenceID
&& ds.ShareGroup == "NBTC"
select new NBTC
{
NBTC_FA_Centroid = ds.CIP_FA_Centroid,
NBTC_FA_Bound = ds.CIP_FA_Boundary,
NBTC_RA_Centroid = ds.CIP_RA_Centroid,
//more properties...
})
.OrderByDescending(n => n.Id) // or some other property that could identify sorting
.FirstOrDefault();
This one has a translation to SQL (LIMIT or TOP depending on backend).

Querying for object based on property of inside object

I have some objects stored in a LiteDB database. I'm trying to get a result of all CostBasisTradeSessionObjects that include Marked objects with a particular name, MarkedNameString. I find the Marked object easily enough, but I dont now how to query for object in object.
public string Marked
{
public ObjectId MarkedId { get; set; }
public String Name { get; set; }
}
public class CostBasisTradeSessionObject
{
public ObjectId CostBasisTradeSessionId { get; set; }
public bool IsActive { get; set; }
public DateTime SessionStarted { get; set; }
public DateTime SessionClosed { get; set; }
public Marked Marked { get; set; }
}
using (var db = new LiteDatabase(#"CostBasesTradeSessionsDatabase.db"))
{
var costBasisTradeSessionObjects = db.GetCollection("costBasisTradeSessionObjects");
Marked marked = db.GetCollection<Marked>("markeds").Find(Query.EQ("Name", "<MarkedNameString>")).Single();
}
So I try to get an result with CostBasisTradeSessionObject objects that includes the marked object returned in var marked.
So I tried a couple of things
var cb = costBasisTradeSessionObjects.Include(x => x.Marked).Equals(marked);
and justing jusing the MarkedNameString directory
var results = costBasisTradeSessionObjects.(Query.("Marked.name", "MarkedNameString"));
or
var results = costBasisTradeSessionObjects.Find(x => x.Marked.Name.Equals("MarkedNameString"));
but all the things I tried return an empty result or dont work.
Regards
I believe you're looking for the Where() method. You can filter your search by your Name property, and return an IEnumerable of CostBasisTradeSessionObject.
var results = costBasisTradeSessionObjects
.Where(x => x.Marked.Name == "MarkedNameString");

How to move string into ViewModel

I am creating a new ViewModel that tally's up the results of a survey, performers some calculations on that data, and then returns the new calculation to a view. I cannot figure out how to include regular "string" data in the collection.
var data = from SurveyResponseModel in db.SurveyResponseModels
group SurveyResponseModel by SurveyResponseModel.MemberId into resultCount
select new ResultsViewModel()
{
MemberId = resultCount.Key,
UseNewTreatmentResult = db.SurveyResponseModels.Count(r => r.UseNewTreatment),
UseBetterTechniqueResult = db.SurveyResponseModels.Count(r => r.UseBetterTechnique),
ChangesOthersResult = db.SurveyResponseModels.First(r => r.ChangesOthers),
};
return View(data);
The first part is counting boolean responses and passing them as an integer back to the ViewModel. The section that includes ChangesOthersResult = db.SurveyResponseModels.First(r => r.ChangesOthers), Should just select the strings from the Model and pass to the ViewModel. I am currently getting a syntax error about changing from type string to bool. I'm not sure what the syntax for this is.
public class SurveyResponseModel
{
[Key]
public int ResponseId { get; set; }
public int MemberId { get; set; }
public int ProgramId { get; set; }
[DisplayName("Use a new treatment")]
public bool UseNewTreatment { get; set; }
[DisplayName("Use better/more updated technique")]
public bool UseBetterTechnique { get; set; }
[DisplayName("Other (please specify):")]
public string ChangesOthers { get; set; }
}
public class ResultsViewModel
{
public int MemberId { get; set; }
public int ProgramId { get; set; }
[DisplayName("Use a new treatment")]
public int UseNewTreatmentResult { get; set; }
[DisplayName("Use better/more updated technique")]
public int UseBetterTechniqueResult { get; set; }
[DisplayName("Other (please specify):")]
public string ChangesOthersResult { get; set; }
}
You need:
ChangesOthersResult = db.SurveyResponseModels.Select(r => r.ChangesOthers)
Or SelectMany. Eventually add FirstOrDefault() at the end depending on what type ChangesOthersResult is and what you actually want to select.
Select gives you a "collection of collections" (I am assuming that ChangesOthers is a collection type). SelectMany a "flattened collection" of the generic type of your ChangesOthers collection. Adding FirstOrDefault() after Select gives you the single collection of the first SurveyResponseModels entity - or null.
Edit
After you supplied the classes I see that ChangesOthers and ChangesOthersResult aren't collections but just of type string. So the line should be:
ChangesOthersResult = db.SurveyResponseModels.Select(r => r.ChangesOthers)
.FirstOrDefault()

Categories