Update items in List<T> c# - 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.

Related

Is there a way to make a IEnumerable method that lets me return any list type in c#?

So currently, you can imagine I have 1 method that is the constructor that funcitons like
info.PersonalInfo=getPersonalInfo(Id);
info.MedicalInfo=getMedicalInfo(Id);
Thing is, all of those get data and get binarys are repeating 95% of the code
using (CVDataEntities data = new CVDataEntities())
{
var temp = data.PersonalInfo.Where(m => m.Id == Id).FirstOrDefault();
return temp;
}
The only thing that changes is instead of PersonalInfo its MedicalInfo.
I thought of using a switch and just sending a number as the selector for which specific object I would want.
But the problem is the method is made so that it can only return
public IEnumerable<PersonalInfo> getPersonalInfo (string Id)
Is there any way for me to make a IEnumerable that lets me return any object, or is there a better way to go about this. I want to do it mostly to reduce the code from 400 lines down to 200 at most.
Try using generic methods, you will be able to specify the return type of your function when you call it. This could make your code look like this :
public IEnumerable<T> getInfo<T>(string id)
{
// Some code
}
// Calling the function
info.PersonalInfo = getInfo<PersonalInfo>(Id);
info.MedicalInfo = getInfo<MedicalInfo>(Id);
But be careful while using it, cause the compiler won't know what type T is (it is only defined at runtime) so it could lead to some errors while processing the data (like missing properties / methods exclusive to a specific type)
EDIT : Johnathan Barclay made a good point by commenting that the // some code bit is relevant and asked "How would the correct property be selected on data? How do you access an Id property on T?"
To get the correct property and access an Id property, you could use System.Reflection and add a string parameter to get the name of the property you want to use, and another to give the Id property name to the function:
public IEnumerable<T> getInfo<T>(string id, string propertyToReadName, string propertyToCompareName)
{
using (CVDataEntities data = new CVDataEntities())
{
// Getting the enumerable not filtered first
IEnumerable<T> unfilteredList = (IEnumerable<T>)data.GetType() // Get the type
.GetProperty(propertyToReadName, typeof(T)) // Get the property (PersonalInfo or MedicalInfo)
.GetValue(data); // Get the value of this property in the `data` instance
// Filtering the list
IEnumerable<T> filteredList = unfilteredList.Where(m =>
typeof(T).GetProperty(propertyToCompareName) // Get the "id" property using parameter
.GetValue(m) // Get the "id" value of m instance
.Equals(id)); // Check if it equals the id given as parameter
return filteredList;
}
}
// Calling the function
info.PersonalInfo = getInfo<PersonalInfo>(Id, "PersonalInfo", "Id");
info.MedicalInfo = getInfo<MedicalInfo>(Id, "MedicalInfo", "Id");
If you want to return a single element instead of an IEnumerable don't forget to change the return type of the function from IEnumerable<T> to T and add .FirstOrDefault() at the return line
Note that you could also give another value to the parameter propertyToCompareName and make a comparison to another property of the T class

Dynamically get a DbSet<T> by Entity class name

I'm trying to use System.Reflections to get a DbSet<T> dynamically from its name.
What I've got right now is:
The DbSet name
The DbSet's Type stored on a variable
The issue I'm facing comes out when trying to use the dbcontext.Set<T>() method, since (these are my tries so far):
When I try to assign to <T> my DbSet Type, it throws me the following compilation error:
"XXX is a variable but is used like a type"
If I try with using both the Extension methods that you will find below in my code (which I made in order to try to get an IQueryable<T>), it returns a IQueryable<object>, which unfortunately is not what I am looking for, since of course when I try to manipulate it with further Reflections, it lacks of all the properties that the original class has…
What am I doing wrong? How can I get a DbSet<T>?
My code is the following, but of course, let me know if you need more infos, clarifications or code snippets.
My Controller's Method:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//FIRST TRY
//throws error: "_type is a variable but is used like a type"
var tableSet = _context.Set<_type>();
//SECOND TRY
//returns me an IQueryable<object>, I need an IQueryable<MyType>
var tableSet2 = _context.Set(_type);
//THIRD TRY
//always returns me am IQueryable<object>, I need an IQueryable<MyType>
var calcInstance = Activator.CreateInstance(_type);
var _tableSet3 = _context.Set2(calcInstance);
//...
}
Class ContextSetExtension
public static class ContextSetExtension
{
public static IQueryable<object> Set(this DbContext _context, Type t)
{
var res= _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
return (IQueryable<object>)res;
}
public static IQueryable<T>Set2<T>(this DbContext _context, T t)
{
var typo = t.GetType();
return (IQueryable<T>)_context.GetType().GetMethod("Set").MakeGenericMethod(typo).Invoke(_context, null);
}
}
EDIT Added TypeFinder's inner code.
In brief, this method does the same of Type.GetType, but searches Type on ALL the generated assemblies
public class TypeFinder
{
public TypeFinder()
{
}
public static Type FindType(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var result = (from elem in (from app in assemblies
select (from tip in app.GetTypes()
where tip.Name == name.Trim()
select tip).FirstOrDefault())
where elem != null
select elem).FirstOrDefault();
return result;
}
}
UPDATE as requested in the comments, here's the specific case:
In my DB i've got some tables which are really similar each other, so the idea was to create a dynamic table-update method which would be good for every table, just passing to this method the table name, the ID of the row to update and the JSON containing data to update.
So, in brief, I would perform some updates on the table given in input as DbSet type, updating the row with ID==id in input with the data contained inside the JSON, which will be parsed inside an object of type X(the same of dbset)/into a dictionary.
In pseudo-code:
public bool MyMethod (string t, int id, string jsonupdate)
{
string _tableName = t;
Type _type = TypeFinder.FindType(_tableName); //returns the correct type
//THIS DOESN'T WORKS, of course, since as said above:
//<<throws error: "_type is a variable but is used like a type">>
var tableSet = _context.Set<_type>();
//parsing the JSON
var newObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonupdate, _type);
//THIS OF COURSE DOESN'T WORKS TOO
//selecting the row to update:
var toUpdate = tableSet.Where(x => x.Id == id).FirstOrDefault();
if(toUpdate!=null)
{
var newProperties = newObj.GetType().GetProperties();
var toUpdateProperties = toUpdate.GetType().GetProperties();
foreach(var item in properties)
{
var temp = toUpdateProperties.Where(p => p.Name==item.Name)
{
//I write it really in briefand fast, without lots of checks.
//I think this is enough, I hope
temp.SetValue(toUpdate, item.GetValue());
}
}
_context.SaveChanges();
}
return false;
}
returns me an IQueryable<object>, I need an IQueryable<MyType>
Well, that will never work. Your IQueryable cannot be of type IQueryable<MyType>because that would mean the compiler would need to know what MyType is and that is not possible, because the whole point of this exercise is to decide that on runtime.
Maybe it's enough to know that those objects are in fact instances of MyType?
If not, I think you have painted yourself into a corner here and you are trying to figure out what paint to use to get out of there. Take a step back, it's probably not a technical problem. Why do you need to do this? Why do you have the conflicting needs of knowing the type at runtime only and knowing it at compile time?
You need to think about your requirements, not about the technical details.
I needed to dynamically load a single record from the database for each type in a list of known types, to print a test email when an admin is editing the template, so I did this:
List<object> args = new List<object>();
//...
//other stuff happens that isn't relevant to the OP, including adding a couple fixed items to args
//...
foreach (Type type in EmailSender.GetParameterTypes())
{
//skip anything already in the list
if (args.Any(a => a.GetType().IsAssignableFrom(type))) continue;
//dynamically get an item from the database for this type, safely assume that 1st column is the PK
string sql = dbContext.Set(type).Sql.Replace("SELECT", "SELECT TOP 1") + " ORDER BY 1 DESC";
var biff = dbContext.Set(type).SqlQuery(sql).AsNoTracking().ToListAsync().Result.First();
args.Add(biff);
}
Caveat: I know at least one record will exist for all entities I'm doing this for, and only one instance of each type may be passed to the email generator (which has a number of Debug.Asserts to test validity of implementation).
If you know the record ID you're looking for, rather than the entire table, you can use dbContext.Set(type).Find(). If you want the entire table of whatever type you've sussed out, you can just do this:
string sql = dbContext.Set(type).Sql; //append a WHERE clause here if needed/feasible, use reflection?
var biff = dbContext.Set(type).SqlQuery(sql).ToListAsync().Result;
Feels a little clunky, but it works. There is strangely no ToList without Async, but I can run synchronously here. In my case, it was essential to turn off Proxy Creation, but you look like you want to maintain a contextful state so you can write back to db. I'm doing a bunch of reflection later, so I don't really care about strong typing such a resulting collection (hence a List<object>). But once you have the collection (even just as object), you should be able to use System.Reflection as you are doing in your UPDATE sample code, since you know the type and can use SetValue with known/given property names in such a manner.
And I'm using .NET Framework, but hopefully this may translate over to .NET Core.
EDIT: tested and working:
public async Task<bool> MyMethod(string _type)
{
Type type = Type.GetType(_type);
var tableSet = _context.Set(type);
var list = await db.ToListAsync();
// do something
}
// pass the full namespace of class
var result = await MyMethod("Namespace.Models.MyClass")
IMPORTANT NOTE: your DbContext need to have the DbSet declared to work!
public class MyContext : DbContext
{
public DbSet<MyClass> MyClasses { get; set; }
}

Variable assignment to first item in IEnumerable does not work

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();

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.

Retrieving item from list

I'm having some problems retrieving information from a list.
In this case I'd like to get the name from a list, eventually I want it turned into a string. This is what I have:
public string ShowName(int patientcode)
{
List<ExtendPatientInfo> patientdata = dk.RetrieveList(patientcode);
string name = patientdata. <What here?>
return name;
}
In the class ExtendPatientInfo I have this, which I think is allright:
private string name;
public ExtendPatientInfo(string name)
{
this.name = name;
}
public string Name
{
get
{
return name;
}
}
I tried using a few things. Like Contains, Find, FindIndex and where. But none of these worked for me because I probably messed something up somewhere. Anybody that can help me?
You have to choose the patient. If it is all the same data for the patient, then you can use LINQ's First
patientdata.First().Name
But, if they are all different, then you could map the list to only have Names
patientdata.Select(x=>x.Name)
At which point, you would still need to iterate through the list to display each name or whatever you need.
As Henk points out, if this is always going to be a list of one item, then you could use
patientdata.Single().Name
*The caveat with Single is from MSDN
Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.
Well you have a list of object and you want to show properties of each object, then you have to access each object in the list and then access it properties. Something like:
foreach(var item in patientdata)
{
Console.WriteLine(item.Name);
//rest of the fields
}
If you want to select a single object from your list then you cause use First / FirstOrDefault , Single / SingleOrDefault depending on your need. But you need an individual object to access it properties. You can't access a property directly from a list.
var item = patientData.FirstOrDefault();
if(item != null)
Console.WriteLine(item.Name);
Try
string name = string.Empty;
var pd = patientData.FirstOrDefault();
if(pd != null)
name = pd.Name
This code gets the first item returned or null.
If it isn;t null it retrieves the Name property value into string variable name.
Incidentally, if you don;t want to learn linq right now you can access List<> via index like:
patientData[0].Name
You will still want to check that patientData[0] is not null before checking the Name property.
You probably want to use Linq. Linq allows you to cycle through a list easily and get what you want.
For example, if you want the patient named "Johan":
ExtendPatientInfo patient = patientdata.Where(x => x.Name == "Johan").FirstOrDefault();
string name = patient.Name;
If I understand your question, you are looking for something like this
ExtendPatientInfo patient =
patientdata.FirstOrDefault(x => x.patientcode == patientcode);
return
(patient != null)
? patient.Name
: null

Categories