How can i update an element in collection instead of the reference - c#

I have a collection ProductSearchResults, below method intends to find a specific product in that collection and update it. I end up updating the object that points to the element of the collection instead of the actual element it self though(i think)
Can you please show me how to do this properly so that I update the actual product in the collection
Thanks
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.Where(p => p.ID == product.ID);
if (productUpdate.Count() > 0)
{
var toUpdate = productToUpdate.First<ProductInfo>();
toUpdate = product;
}
}

IN actual fact all you are doing is changing the reference to the local variable toUpdate to point at the passed-in argument product.
Lets take a step backwards, when you do:
var toUpdate = productToUpdate.First<ProductInfo>();
you have a reference to an item from your collection (ProductSearchResults). You can now happily update its properties, ala:
toUpdate.ProductName = product.ProductName;
toUpdate.Price = product.Price;
//etc..
however, you cannot update the itemn in the collection to point to a different/new item in the way you were attempting to. You could remove that item from the collection, and add your new one if that is indeed what you require:
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.Where(p => p.ID == product.ID);
if (productUpdate.Count() > 0)
{
var toUpdate = productToUpdate.First<ProductInfo>();
this.ProductSearchResults.Remove(toUpdate);
this.ProductSearchResults.Add(product);
}
}
Hope that helps.

var productToUpdate = this.ProductSearchResults.FirstOrDefault(p => p.ID == product.ID);
if (productUpdate != null)
{
productUpdate.Property = product.Property;
...continue for other properties
}

In C#, writing variable = expression; assigns a new value to the variable. It does not affect the value that was previously referenced by that variable.
In your example, you have a value called product, which is a ProductInfo provided by the caller, and a value you get from your list, which you stored in toUpdate, that you wish to update. This would involve calling member functions (or assigning to properties) of toUpdate based on values in product.
However, I suspect you actually want to update the product information of a product found in a database or other kind of storage that is accessed by ProductSearchResults. The storage engine you use (or your data access layer) probably provides you with a function to save a ProductInfo associated with a certain identifier, so all you need to do is call that function.

Do you want to replace the product in the collection or update some properties on it? Assuming the latter, here's an example:
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.FirstOrDefault(p => p.ID == product.ID).;
if (productToUpdate == null)
{
// throw exception?
}
else
{
productToUpdate.Price = productInfo.Price; // for example
}
}

Related

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; }
}

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.

EF: Duplicate Object with all child including sub sub children (Deep Copy)

I have a table with properties like:
Id Name ParentId
ParentId is a foreign key to primary column Id. Now lets say I have a few rows like: (Only showing ParentId from rows)
NULL
/ \
1 2
/ \
3 4
Now, lets say we want to copy row object whose ParentId is NULL with it's all sub objects.
var row = db.FirstOrDefault(x=> x.Id == 1);
var new_row = new Table1();
var subrows = row.Table1.ToArray();
foreach(var row in subrows)
{
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
new_row.Table1 = subrows;
db.Table.Add(new_row);
db.saveChanges();
Result: New inserted structure like:
NULL
/ \
1 2
I am assuming that only one sublevel is being copied. How to copy/insert all sublevels?
EDIT: Since detach was helping create a copy till one level, this is what I tried:
private void RecursiveDetach(Table1 parent)
{
var subrows = parent.Table1.ToArray();
foreach (var row in subrows)
{
if(row.Table1.Count() > 0)
{
RecursiveDetach(row);
}
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
}
However, now I am getting an error:
Collection was modified; enumeration operation may not execute.
I've had to do this before. I've done it purely in code, recursively copying objects and sanitizing unique IDs where needed, but the cleanest approach I've ever built is serializing the object to XML, then de-serializing into a new object. The approach is less efficient, but fantastically flexible and easy to implement.
//Save object to XML file. Returns filename.
public string SaveObjectAsXML(int id)
{
//however you get your EF context and disable proxy creation
var db = GetContext();
bool currentProxySetting = db.Configuration.ProxyCreationEnabled;
db.Configuration.ProxyCreationEnabled = false;
//get the data
var item = db.GetItem(id); //retrieval be unique to your setup, but I have
//a more generic solution if you need it. Make
//sure you have all the sub items included
//in your object or they won't be saved.
db.Configuration.ProxyCreationEnabled = currentProxySetting;
//if no item is found, do whatever needs to be done
if (item == null)
{
return string.Empty;
}
//I actually write my data to a file so I can save states if needed, but you could
//modify the method to just spit out the XML instead
Directory.CreateDirectory(DATA_PATH); //make sure path exists to prevent write errors
string path = $"{DATA_PATH}{id}{DATA_EXT}";
var bf = new BinaryFormatter();
using (FileStream fs = new FileStream(path, FileMode.Create))
{
bf.Serialize(fs, repair);
}
return path;
}
//Load object from XML file. Returns ID.
public int LoadXMLData(string path)
{
//make sure the file exists
if (!File.Exists(path))
{
throw new Exception("File not found.");
}
//load data from file
try
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
var item = (YourItemType)new BinaryFormatter().Deserialize(fs);
db.YourItemTypes.Add(item);
db.SaveChanges();
return item.Id;
}
}
catch (Exception ex) {
//Exceptions here are common when copying between databases where differences in config entries result in mis-matches
throw;
}
}
The use is simple.
//save object
var savedObjectFilename = SaveObjectAsXML(myObjID);
//loading the item will create a copy
var newID = LoadXMLData(savedObjectFilename);
Best of luck!
Here's a second, totally different answer: recursively detach your whole object instead of just the parent object. The following is written as an extension method to your context object:
/// <summary>
/// Recursively detaches item and sub-items from EF. Assumes that all sub-objects are properties (not fields).
/// </summary>
/// <param name="item">The item to detach</param>
/// <param name="recursionDepth">Number of levels to go before stopping. object.Property is 1, object.Property.SubProperty is 2, and so on.</param>
public static void DetachAll(this DbContext db, object item, int recursionDepth = 3)
{
//Exit if no remaining recursion depth
if (recursionDepth <= 0) return;
//detach this object
db.Entry(item).State = EntityState.Detached;
//get reflection data for all the properties we mean to detach
Type t = item.GetType();
var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod()?.IsPublic == true) //get only properties we can set
.Where(p => p.PropertyType.IsClass) //only classes can be EF objects
.Where(p => p.PropertyType != typeof(string)) //oh, strings. What a pain.
.Where(p => p.GetValue(item) != null); //only get set properties
//if we're recursing, we'll check here to make sure we should keep going
if (properties.Count() == 0) return;
foreach (var p in properties)
{
//handle generics
if (p.PropertyType.IsGenericType)
{
//assume its Enumerable. More logic can be built here if that's not true.
IEnumerable collection = (IEnumerable)p.GetValue(item);
foreach (var obj in collection)
{
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
else
{
var obj = p.GetValue(item);
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
}
The biggest thing to look out for will be config-type properties--object that represent data not directly related to the object. These might create conflicts, so better to make sure your object doesn't include them.
Note:
This approach requires that all sub-objects you wish to copy be populated in advance, avoiding lazy loading. To ensure this, I use the following extension for my EF queries:
//Given a custom context object such that CustomContext inherits from DbContext AND contains an arbitrary number of DbSet collections
//which represent the data in the database (i.e. DbSet<MyObject>), this method fetches a queryable collection of object type T which
//will preload sub-objects specified by the array of expressions (includeExpressions) in the form o => o.SubObject.
public static IQueryable<T> GetQueryable<T>(this CustomContext context, params Expression<Func<T, object>>[] includeExpressions) where T : class
{
//look through the context for a dbset of the specified type
var property = typeof(CustomContext).GetProperties().Where(p => p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericArguments()[0] == typeof(T)).FirstOrDefault();
//if the property wasn't found, we don't have the queryable object. Throw exception
if (property == null) throw new Exception("No queryable context object found for Type " + typeof(T).Name);
//create a result of that type, then assign it to the dataset
IQueryable<T> source = (IQueryable<T>)property.GetValue(context);
//return
return includeExpressions.Aggregate(source, (current, expression) => current.Include(expression));
}
This method assumes that you have a custom context object that inherits from DbContext and contains DbSet<> collections of your objects. It will find the appropriate one DbSet<T> and return a queryable collection that will pre-load specified sub-classes in your object. These are specified as an array of expressions. For example:
//example for object type 'Order'
var includes = new Expression<Func<Order, object>>[] {
o => o.SalesItems.Select(p => p.Discounts), //load the 'SalesItems' collection AND the `Discounts` collection for each SalesItem
o => o.Config.PriceList, //load the Config object AND the PriceList sub-object
o => o.Tenders, //load the 'Tenders' collection
o => o.Customer //load the 'Customer' object
};
To retrieve my queryable collection, I now call it as such:
var queryableOrders = context.GetQueryable(includes);
Again, the purpose here is to create a queryable object that will eagerly load only the sub-objects (and sub-sub-objects) that you actually want.
To get a specific item, use this like any other queryable source:
var order = context.GetQueryable(includes).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
Note that you can also provide the include expression inline; however, you will need to specify the generic:
//you can provide includes inline if you just have a couple
var order = context.GetQueryable<Order>(o => o.Tenders, o => o.SalesItems).FirstOrDefault(o => o.OrderNumber == myOrderNumber);

Update a list of entities with corresponding fields equal to an external list

I have an entity called Hazaa containing two fields: Killed (type DateTime?) and Linky (type Guid). The business logic dictates that whenever we create a new instance of Hazaa, the old one is supposed to be killed by setting the time stamp.
So, doing it for a single element, I apply the method as follows.
public void Create(Hazaa newbie)
{
using (ModelContainer context = new ModelContainer())
{
Hazaa oldie = context.Hazaas
.Single(hazaa => hazaa.Linky == hazaa.Linky && !hazaa.Killed.HasValue);
oldie.Killed = DateTime.Now;
context.Hazaas.AddOrUpdate(oldie);
context.Hazaas.Add(newbie);
context.SaveChanges();
}
}
Now, I'd like to use a similar approach for bulk update. The signature of that method would be following.
public void Create(List<Hazaa> newbies)
{
...
}
My problems is that I'm not sure how to perform the selection of pre-existing hazaas given the list of new additions. One way is to apply foreach statement and execute them one by one (but that's a slow method). Another one would be to use Contains method (but that's a problem because the list might long).
Are there any other options? My colleague suggested the following. I feel that it might be refactored so that it doesn't access context so many times. Do I worry without reason, perhaps?
public void Create(List<Hazaa> newbies)
{
...
using (ModelContainer context = new ModelContainer())
{
List<Hazaa> oldies = newbies.Select(hazaa => context.Hazaas
.Single(oldie => oldie.Linky == hazaa.Linky && !oldie.Killed.HasValue))
.ToList();
...
}
...
}
Since you use Single I assume that all Linkys represent existing records. This means that you can update them without even fetching them from the database first:
using (ModelContainer context = new ModelContainer())
{
foreach(var hazaa in newbies)
{
context.Attach(hazaa);
hazaa.Killed = DateTime.Now; // Will mark hazaa as modified
context.Add(new Hazaa { Linky = hazaa.Linky };
context.Hazaas.Add(newbie);
}
context.SaveChanges();
}

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