Variable assignment to first item in IEnumerable does not work - c#

I am attempting to assign the value true to a field in my collection of objects. I am using the First() method to retrieve the first object, and assign to it. In this example, I am assigning the value true to the Show variable. However, immediately after the assignment, it appears that Show variable is still false:
public class CallerItem
{
public int IndexId;
public string PhoneNumber;
public bool ToInd;
public bool Show;
}
public void myFunc() {
var callers = dbCallerRecs.Select(x => new CallerItem() { IndexId = x.IndexId, PhoneNumber = x.PhoneNumber, ToInd = x.ToInd });
var toCallers = callers.Where(x => x.ToInd);
if (toCallers.Any())
{
toCallers.First().Show = true;
Console.Log(toCallers.First().Show); //THIS LOGS 'false'. HOWEVER, IT SHOULD LOG 'true'
}
}
Is there something I am missing? Perhaps my understanding of the references returned from the Where clause is not right?

if (toCallers.Any())
{
toCallers.First().Show = true;
Console.Log(toCallers.First().Show); //THIS LOGS 'false'. HOWEVER, IT SHOULD LOG 'true'
}
Every time you call .First() you are getting the first item. For some enumerables (e.g. IQueryable) it will return a different object every time.
The below code will call the method only once and thus avoid the issue. Note also that I have used FirstOrDefault rather than Any then First - since the former will result in fewer DB queries (i.e. be faster).
var caller = toCallers.FirstOrDefault().
if (caller != null)
{
caller.Show = true;
Console.Log(caller.Show);
}

var callers = dbCallerRecs.Select(x => new CallerItem() { IndexId = x.IndexId, PhoneNumber = x.PhoneNumber, ToInd = x.ToInd });
var toCallers = callers.Where(x => x.ToInd);
defines a query which is evaluated when some elements in the resulting IEnumerable<CallerItem> (or IQueryable<CallerItem> which implements IEnumerable<CallerItem>) is iterated. This happens three times in your code - when calling Any and both times you call First (assuming .Any() returns true).
The reason you see this behaviour is the two calls to First cause the query to be re-evaluated and a new object to be created for each call, so you're modifying a different object the one you end up logging.
One solution would be to eagerly evaluate the query:
var toCallers = callers.Where(x => x.ToInd).ToList();

Related

Calling Select inside of List<> extended method in C#

Just wondering why a Select call won't execute if it's called inside of an extended method?
Or is it maybe that I'm thinking Select does one thing, while it's purpose is for something different?
Code Example:
var someList = new List<SomeObject>();
int triggerOn = 5;
/* list gets populated*/
someList.MutateList(triggerOn, "Add something", true);
MutateList method declaration:
public static class ListExtension
{
public static IEnumerable<SomeObject> MutateList(this IEnumerable<SomeObject> objects, int triggerOn, string attachment, bool shouldSkip = false)
{
return objects.Select(obj =>
{
if (obj.ID == triggerOn)
{
if (shouldSkip) shouldSkip = false;
else obj.Name += $" {attachment}";
}
return obj;
});
}
}
The solution without Select works. I'm just doing a foreach instead.
I know that the Select method has a summary saying: "Projects each element of a sequence into a new form." But if that were true, then wouldn't my code example be showing errors?
Solution that I used (Inside of the MutateList method):
foreach(SomeObject obj in objects)
{
if (obj.ID == triggerOn)
{
if (shouldSkip) shouldSkip = false;
else obj.Name += $" {attachment}";
}
});
return objects;
Select uses deferred execution, meaning that it does not actually execute until you try to iterate over the results, with a ForEach, or using Linq methods that require the actual results like ToList or Sum.
Also, it returns an iterator, it does not run on the items in-place, but you're not capturing the return value in your calling code.
For those reasons - I would recommend not using Select to mutate the object in the list. You're just wrapping a ForEach call in a less clean way. I would just use ForEach within the method.

Update items in List<T> c#

I have a class to handle some data :
public class User
{
public string Name;
public string Date;
}
In another class,i create a List of User class and add data as follows :
public class Display
{
List<User> userData = new List<User>;
private void add()
{
User udata = new User;
udate.Name = "Abc";
udata.Date = "1/1/18";
userData.Add(udata);
}
}
My question is, after adding some data,how do i update it ? Say i have added a data(udata is what i mean) with a Name of ABC,how do i update it?
Since your list contains a mutable type, all you need to do is get a reference to the specific item you want to update.
That can be done in a number of ways - using it's index, using the Find method, or using linq are the first three that comes to mind.
Using index:
userData[0]?.Name = "CBA";
Using Find:
userData.Find(u => u.Name = "Abc")?.Name = "CBA";
Using linq (FirstOrDefault is not the only option):
userData.FirstOrDefault(u => u.Name = "Abc")?.Name = "CBA";
Note the use of null conditional operator (]? and .?) it prevents a null reference exception in case the item is not found.
Update
As Ak77th7 commented (thanks for that!), the code in this answer wasn't tested and will cause a compilation error -
error CS0131: The left-hand side of an assignment must be a variable,
property or indexer
The reason for this is the null-conditional operator (?.).
You can use it to get values from properties, but not for setting them.
The fix is either to accept the fact that your code might throw a NullReferenceException (which I personally believe has no room in production-grade code) or to make your code a bit more cumbersome:
// Note: Possible null here!
userData.Find(u => u.Name.EndsWith("1")).Name = "Updated by Find";
// Safe, but cumbersome
var x = userData.FirstOrDefault(u => u.Name.EndsWith("2"));
if(x is not null)
{
x.Name = "Updated by FirstOrDefault";
}
See a live demo on SharpLab.IO
Nothing tricky, really (but does use System.Linq)
**EDIT: Changed Single to First to avoid error if there are two users with the same name. **
void Update(string name, string newName)
{
var user = userData.First(u => u.Name == name);
user.Name = newName;
}
Notice this changes the object, and the List maintains reference to the changed object.

What is wrong with this object being capable of telling how many of its properties are not null/whitespace/empty?

I am trying to figure out why the following code throws a StackOverflowException (I am finally posting a StackoverflowException in SO!).
Debugging seems to point out that the p.GetValue(this) is generating further calls.
What is actually triggering the infinite call chain ? Is it because p.GetValue(this) ultimately returns an instance of the current object, and thus acts as if constructing a new instance (and every object that constructs itself within its construction will lead to Stackoverflow exceptions) ?
My intent with the following code is to have an object being capable of telling how many of its properties have null/space/empty values.
public class A
{
public string S1 { get; set; }
public string S2 { get; set; }
public int NonInitializedFields
{
get
{
int nonNullFields = 0;
var properties = this.GetType().GetProperties();
foreach (var p in properties)
{
var value = p.GetValue(this);
if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
nonNullFields++;
}
return nonNullFields;
}
}
}
//the following throws a StackOverflowException (the construction line itself)
A a1 = new A1{ S1 = "aaa"};
Console.WriteLine(a1.NonInitializedFields);
P.S. My idea originally involves only simple string properties, nothing else, so whatever problems may arise with this approach with other types are not relevant.
You have a property, which, when you execute the "get" accessor, finds all the properties and fetches their value. So it executes itself, recursively.
If you only want string properties, you should check the property type before fetching the value:
var properties = GetType().GetProperties().Where(p => p.PropertyType == typeof(string));
At that point, as your NonInitializedFields property doesn't have a return type of string, it won't be executed.
Personally I would make this a method call rather than a property anyway, mind you. That would also fix the issue, as the method wouldn't find itself when looking for properties.
I would also rename it, as:
A property isn't necessarily backed by a field
A field could be explicitly initialized as null or a reference to a string containing only whitespace
A method called GetNonWhitespaceStringPropertyCount() would be more accurate, IMO. You can also make the whole implementation a LINQ query:
return GetType().GetProperties()
.Where(p => p.PropertyType == typeof(string))
.Select(p => p.GetValue(this))
.Count(v => !string.IsNullOrWhitespace((string) v));
Note that I've fixed the next issue in your code - you're meant to be counting non-null/empty values, but you're actually counting null/empty ones.

IEnumerable Select statement with ternary operator

I have an odd behavior using an IEnumerable<string> with a ternary operator and a Select statement.
I have two lists with different objects. One list contains Enums the other list contains objects. Those objects do have a String property.
If one list is null or empty I want to get the values of the other list.
Here is some code:
public class ExportItem
{
public string Type;
...
}
public enum ExportType
{
ExportType1,
ExportType2,
...
}
The List<ExportItem> is always filled by a config file. The List<ExportType> is filled if command line arguments are provided. So if List<ExportType> is filled I want to use them, otherwise I want to use those from the config file.
So my code ist like this:
IEnumerable<string> exportTypes = MyListOfExportTypes != null &&
MyListOfExportTypes.Any() ? MyListOfExportTypes.Select(x => x.ToString()) :
MyListOfExportItems.Select(x => x.Type);
The thing is that exportTypes is null but I don't get it...
When I do this with if-else everything works as expected. Also if exportTypes is of type List<string> and I call ToList() after the Select statement everything works fine.
Using var a = MyListOfExportTypes.Select(x => x.ToString()); and var b = MyListOfExportItems.Select(x => x.Type); does work as expected.
Must be something with the ternary operator and/or IEnumerable. But what?
Or what do I miss? Any suggestions?
EDIT:
I now have a screenshot...
Note that the code above foreach works nevertheless...
Not sure if this was answered,
But I think that this is related to the fact that you are using LINQ deferred execution.
When writing LINQ queries,
there is a difference between creating the query and executing it.
Writing the select statement, is creating the query, adding ToList() executes it.
Think of it like writing SQL query in SQL server console (that's the writing stage),
and once you hit F5 (Or the play button) you execute it.
I hope this little code sample will help to clarify it.
public class SomeClass
{
public int X { get; set; }
public int Y { get; set; }
public void Test()
{
//Here I'm creating a List of Some class
var someClassItems = new List<SomeClass> {
new SomeClass { X = 1, Y = 1 },
new SomeClass { X = 2, Y = 2 }
};
//Here I'm creating a Query
//BUT, I'm not executing it, so the query variable, is represented by the IEnumerable object
//and refers to an in memory query
var query = someClassItems.
Select(o => o.X);
//Only once the below code is reached, the query is executed.
//Put a breakpoint in the query, click the mouse cursor inside the select parenthesis and hit F9
//You'll see the breakpoint is hit after this line.
var result = query.
ToList();
}
}

Entity Framework, how to avoid this issue?

I have a method like this:
public FbUser FindUserByGraphOrInsert(dynamic json, bool commit = false)
{
string graphId = json.id;
EntityDataModelContext context = DataContext.GetDataContext();
FbUser user = context.FbUsers.FirstOrDefault(u => u.FbGraphId == graphId);
if (user == null)
{
user = new FbUser();
user.FbGraphId = json.id;
user.FbUsername = StringExtensions.UnicodeDecode(json.name);
context.FbUsers.AddObject(user);
if (commit)
context.SaveChanges();
}
return user;
}
I call this method repeatedly in a loop (say upwards of 80 times), with commit = false
Thing is, I expected this method to let me know if the user is already in the context, but this doesn't seem to be the case.
The result is that when I finally save changes, I get a list of 80 users, where 27 are distinct.
I expect this method to return those 27, how could I change it to achieve this?
Do I really need to save changes every single time?
You cant 'simply' do that, the problem is that each query will always hit the database by default since EF has no way of knowing you either query the same data or that there have been no underlying changes in the database since you opened the connection.
You can however check the ChangeTracker/ObjectStateManager for existing changed objects and query that one as well prior to deciding to add a new object.
Sample:
var addedObjects = context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.OfType<MyEntity>().Where(x => x.Name == newObject.Name);
Based on Polity's answer, I implemented the following extension method, which worked.
public static IEnumerable<T> IncludeUnsaved<T>(this ObjectSet<T> set) where T : class
{
var addedObjects = set.Context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.Select(e => e.Entity).OfType<T>();
return equalObjects.Concat(set);
}

Categories