Update specific object in array - c#

I have a DataTable and an array of objects that I loop through.
For each row in a data table, I search through my collection of objects with Linq, and if found, that object needs to be updated.
But how do I refresh my collection without reloading it from the database?
Car[] mycars = Cars.RetrieveCars(); //This is my collection of objects
//Iterate through Cars and find a match
using (DataTable dt = data.ExecuteDataSet(#"SELECT * FROM aTable").Tables[0])
{
foreach (DataRow dr in dt.Rows) //Iterate through Data Table
{
var found = (from item in mycars
where item.colour == dr["colour"].ToString()
&& item.updated == false
select item).First();
if (found == null)
//Do something
else
{
found.updated = true;
Cars.SaveCar(found);
//HERE: Now here I would like to refresh my collection (mycars) so that the LINQ searches on updated data.
//Something like mycars[found].updated = true
//But obviously mycars can only accept int, and preferably I do not want to reload from the database for performance reasons.
}
How else can I search and update a single item in the array?

You don't need to update your collection - assuming Car is a class, you've already updated the object that the array refers to by setting found.updated to true.
Don't forget that the array only contains references - so the found reference is the same reference which is in the array; updating the object via either variable will result in the change being visible via the other one.

Related

How to change items in cache

Hello i want to change and alter values inside the cache of my acumatica cache i would like to know how to do it
for example i want to change the Ext. Cost value pro grammatically of the first line or the second line or can i check if there is already a "Data Backup" on transaction Descr.
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
if (Globalvar.GlobalBoolean == true)
{
PXCache cache = Base.Transactions.Cache;
APTran red = new APTran();
red.BranchID = Base.Transactions.Current.BranchID;
red.InventoryID = 10045;
var curyl = Convert.ToDecimal(Globalvar.Globalred);
red.CuryLineAmt = curyl * -1;
cache.Insert(red);
}
else
{
}
baseMethod();
}
this code add a new line on persist but if it save again it add the same line agaub u wabt ti check if there is already a inventoryID =10045; in the cache
thank you for your help
You can access your cache instance by using a view name or cache type. Ex: (Where 'Base' is the graph instance)
Base.Transactions.Cache
or
Base.Caches<APTran>().Cache
Using the cache instance you can loop the cached values using Cached, Inserted, Updated, or Deleted depending on which type of record you are looking for. You can also use GetStatus() on an object to find out if its inserted, updated, etc. Alternatively calling PXSelect will find the results in cache (PXSelectReadOnly will not).
So you could loop your results like so:
foreach (MyDac row in Base.Caches<MyDac>().Cache.Cached)
{
// logic
}
If you know the key values of the cache object you are looking for you can use Locate to find by key fields:
var row = (MyDac)Base.Transactions.Cache.Locate(new MyDac
{
MyKey1 = "",
MyKey2 = ""
// etc... must include each key field
});
As Mentioned before you can also just use a PXSelect statement to get the values.
Once you have the row to update the values you set the object properties and then call your cache Update(row) before the base persist and you are good to go. Similar if needing to Insert(row) or Delete(row).
So in your case you might end up with something like this in your persist:
foreach (APTran row in Base.Transactions.Cache.Cached)
{
if (Globalvar.GlobalBoolean != true || row.TranDesc == null || !row.TranDesc.Contains("Data Backup"))
{
continue;
}
//Found my row
var curyl = Convert.ToDecimal(Globalvar.Globalred);
row.CuryLineAmt = curyl * -1;
Base.Transactions.Update(row);
}

Alternative to setting values in foreach C#

I'm rather new to MVC/C# and from what I understand, foreach is read-only.
I would like there to be a method that sets the values that are null to false if that method is called.
IQueryable<CurrentMatch> FindTheMatch = (from row in db.CurrentMatches
where row.UserId.ToString() == UserIdentity
where row.MatchID == MatchIdentity
select row);
List<CurrentMatch> SetRemainingValuesToFalse = FindTheMatch.ToList();
I know that the part below wont work, it just demonstrates how I'm trying to achieve what I want to do.
foreach (var Column in SetRemainingValuesToFalse)
{
if (Column == null)
{
Column = false;
}
}
As the row has a large number of properties it wouldn't be scaleable in the future to set each property manually.
You just need to use a standard for loop instead of a foreach. You can't modify the collection inside a foreach because that is how the iterator works. You can however modify values on the objects themselves.
See also: Changing objects value in foreach loop?
I think you have this sort of the wrong way round. If you set that value to false inside any sort of loop, the context is lost when you exit that iteration of the loop.
Instead, what you probably want to do is, when consuming the list, treat nulls as false. You can use the null coalesce operator for this (??)
foreach (var row in FindTheMatch)
{
DoSomethingInterestingWith(row.Column ?? false); // pass false if Column is null.
}
for(int i=0;i<SetRemainingValuesToFalse.length;i++)
{
if (SetRemainingValuesToFalse[i] == null)
{
SetRemainingValuesToFalse[i] = false;
}
}
you are slightly misunderstanding how the foreach is working
foreach(var c in col)
reads as
While col.asEnumerable.HasValues let c = col.asEnumerable.Current
because of this you can't change either the enumerable or its current value with out breaking the loop, however if the enumerable isn't attached to the collection you are changing then you have no problems
ToList for example will clone the collection meaning the enumerable is attached to the clone not the original collection
foreach(var c in col)
col.Remove(c);
will error
foreach(var c in col.ToList())
col.Remove(c);
works fine
like wise
foreach(var c in col)
if(c.Field == null) c.Field = false;
is also fine because you are editing the the content of the current enumerable location not the location itself
however your stated desire of just replacing nulls in a collection is much simpler
col.Select(c=>c??false); //c#6
col.Select(c=>c == null? false : c); //c#<6
as you seem to be working with something akin to a datatable then you could do this
foreach(var row in table.Rows)
foreach(var col in table.Columns)
row[col] = row[col] ?? false;

Access my class returned list collection in code behind

I have a list collection in my class library that uses a sql datareader to returns a list of family details
public class Dataops
{
public List<Details> getFamilyMembers(int id)
{
some of the database code..
List<Details> fammemdetails = new List<Details>();
Details fammember;
while (reader.Read())
{
fammemdetails = new Details((
reader.GetString(reader.GetOrdinal("PHOTO")));
fammemdetails.add(fammember);
}
return fammemdetails;
}
}
So i reference the dll to my project and would like to bind an image to one of my datareader values.
MyProject
DataOps ops = new DataOps();
myimage.ImageUrl = ??? (how do i access the list collections return image value here?
I am able to bind a datasource to the entire method like so
dropdownlistFamily.DataSource = mdb.GetFamilyMembers(id);
But cant figure out how to just grab a single value from there
You can use First/FirstOrDefault, Single/SingleOrDefault depending on your requirement. This would give you a single item from the List and you can access its ImageUrl property.
var item = mdb.GetFamilyMembers(id).FirstOrDefault();
if(item != null)
myimage.ImageUrl = item.ImageUrlProperty;
If you want to get some specific object from the list based on the condition then you can do:
var item = mdb.GetFamilyMembers(id).FirstOrDefault(r=> r.ID == someID);
You may see: LINQ Single vs SingleOrDefault vs First vs FirstOrDefault
You can use FirstOrDefault or SingleOrDefault. Or specify a predicate and use Where.
var firstValue = ops.getFamilyMembers(1).FirstOrDefault();
Use index to access particular record in the collection. You will need to ensure that element exists at the index you given in indexer, otherwise you will get exception. It is zero based index so first element will be at zero index.
var familyMembers = mdb.GetFamilyMembers(id);
if(familyMembers.Count > 0)
myimage.ImageUrl = familyMembers[0].ImageURLProperty;

Cache only parts of an object

I'm trying to achieve a super-fast search, and decided to rely heavily on caching to achieve this. The order of events is as follows;
1) Cache what can be cached (from entire database, around 3000 items)
2) When a search is performed, pull the entire result set out of the cache
3) Filter that result set based on the search criteria. Give each search result a "relevance" score.
4) Send the filtered results down to the database via xml to get the bits that can't be cached (e.g. prices)
5) Display the final results
This is all working and going at lightning speed, but in order to achieve (3) I've given each result a "relevance" score. This is just a member integer on each search result object. I iterate through the entire result set and update this score accordingly, then order-by it at the end.
The problem I am having is that the "relevance" member is retaining this value from search to search. I assume this is because what I am updating is a reference to the search results in the cache, rather than a new object, so updating it also updates the cached version. What I'm looking for is a tidy solution to get around this. What I've come up with so far is either;
a) Clone the cache when i get it.
b) Create a seperate dictionary to store relevances in and match them up at the end
Am I missing a really obvious and clean solution or should i go down one of these routes? I'm using C# and .net.
Hopefully it should be obvious from the description what I'm getting at, here's some code anyway; this first one is the iteration through the cached results in order to do the filtering;
private List<QuickSearchResult> performFiltering(string keywords, string regions, List<QuickSearchResult> cachedSearchResults)
{
List<QuickSearchResult> filteredItems = new List<QuickSearchResult>();
string upperedKeywords = keywords.ToUpper();
string[] keywordsArray = upperedKeywords.Split(' ');
string[] regionsArray = regions.Split(',');
foreach (var item in cachedSearchResults)
{
//Check for keywords
if (keywordsArray != null)
{
if (!item.ContainsKeyword(upperedKeywords, keywordsArray))
continue;
}
//Check for regions
if (regionsArray != null)
{
if (!item.IsInRegion(regionsArray))
continue;
}
filteredItems.Add(item);
}
return filteredItems.OrderBy(t=> t.Relevance).Take(_maxSearchResults).ToList<QuickSearchResult>();
}
and here is an example of the "IsInRegion" method of the QuickSearchResult object;
public bool IsInRegion(string[] regions)
{
int relevanceScore = 0;
foreach (var region in regions)
{
int parsedRegion = 0;
if (int.TryParse(region, out parsedRegion))
{
foreach (var thisItemsRegion in this.Regions)
{
if (thisItemsRegion.ID == parsedRegion)
relevanceScore += 10;
}
}
}
Relevance += relevanceScore;
return relevanceScore > 0;
}
And basically if i search for "london" i get a score of "10" the first time, "20" the second time...
If you use the NetDataContractSerializer to serialize your objects in the cache, you could use a [DataMember] attribute to control what gets serialized and what doesn't. For instance, you could store your temporarary calculated relevance value in a field that is not serialized.

Adding a Row to a DataTable question

Greetings everyone-
In my code below I'm trying to add a Row from an existing DataTable (dtResult) into a new DataTable (dtCopyResult) if email address does not match. So I guess my knowledge of ADO.NET is not up to par because whenever I try to run my below code, I get an "This Row already belongs to another table". Please let me know how I can fix this..
Many Thanks
if (checkBox1.Checked)
{
for (int i = dtResult.Rows.Count - 1; i >= 0; i--) //dtResult is a DataTable
{
foreach (object email in emails) //emails is an ArrayList of email addresses
{
if (email.ToString().ToUpper() != dtResult.Rows[i][3].ToString().ToUpper())
{
dtCopyResult.Rows.Add(dtResult.Rows[i]); //dtCopyResult is a new blank DataTable that I'm trying to add rows to
}
}
}
}
As the error message tells you, a DataRow belongs to a particular DataTable; you cannot just take it and add it to another one. What you can do is either
create a new DataRow and fill it with the values from the old DataRow or
use the DataTable.ImportRow method:
dtCopyResult.ImportRow(dtResult.Rows[i]);
You can use ImportRow function, full example here http://support.microsoft.com/kb/308909
One thing I noticed is that the new row will get added multiple times; once for each item in the emails collection.
You either need to keep a local list of items already added or loop through dtCopyResult to make sure you have not already added the email.
List<string> alreadyAdded = new List<string>();
if (email.ToString().ToUpper() != dtResult.Rows[i][0].ToString().ToUpper()
&& !alreadyAdded.Contains(email.ToString()))
{
dtCopyResult.ImportRow(_dt1.Rows[i]);
alreadyAdded.Add(email.ToString());
}
it means the adding row is belong to "dtResult" and DataRow is an "Object" that represent data. Not data itselfs. so in this case u try to add DataRow object that belong to another table which will error.
another way to do is copy everything to dtCopy and delete it if condition mismatch.
Array is mainly to used to stored various type of object. In this case, if u gonna store only email u should use
List<string> emails = new List<string>();
emails.Add("test#example.com");
to enumerate rows for deleteing data
List<DataRow> removing = new List<DataRow>();
foreach(var row in table.AsEnumerable()) // or table.Rows
if(...condition)removing.Add(row);
foreach(var row in removing)table.Rows.Remove(row);
the reason to use 'removing' is if u loop through rows and removing it at the same time, means u change the enumerate which will cause error. becus .Net is not happy when u pull out something its looping.
Try replacing
dtCopyResult.Rows.Add(dtResult.Rows[i]);
with
DataRow rowToBeCopied = dtCopyResult.NewRow();
//Copy the row values..
rowToBeCopied.X = dtResult.Rows[i].X;
//Copy the remaining row values
dtCopyResult.Rows.Add(rowToBeCopied);

Categories