Alright, so I need to get the key value paired differences of two data rows. In short, I'm sending an email to let a user know they've made specific changes to their profile. I already know the rows are different because I'm using the SequenceEqual to determine that.
At the moment I've written and debugged the following code:
if (currentRow.ItemArray.SequenceEqual(updatedRow)) { return; }
var updates = currentRow.ItemArray
.Where((o, i) =>
{
if (o == null && updatedRow[i] == null) { return false; }
else if (o == null && updatedRow[i] != null) { return true; }
else if (o.Equals(updatedRow[i])) { return false; }
return true;
})
.Select((o, i) =>
{
return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedRow[i])
};
}).ToList();
But there are two problems with this code:
It seems really inefficient to me because it's going through each value in the ItemArray and then building a key value pair if the values differ.
It doesn't actually work because the i sent into the Select isn't correct (e.g. if the second column changed, 1, the index sent into the Select is actually 0. Honestly, that makes sense, but I'm not sure exactly how to get what I want here.
CONSTRAINT: I'd like to use LINQ here.
NOTE: I'm only comparing two rows (i.e. it's not going to be going through a list of rows).
What is the appropriate LINQ statement for what I'm trying to do here?
UPDATE: It really feels like I just need to use:
currentRow.ItemArray.Intersect(updatedRow.ItemArray)
but the problem with that is I don't have any idea what field that is so I can't build a key value pair. In other words, I get back only the differences, but I've no clue what the index is so I can't go get a column name based off of those values.
Honestly you're not going to lose much code clarity by using a for loop.
public IEnumerable<AppServices.NotificationData> GetUpdates(DataRow currentRow, DataRow updatedRow)
{
if (currentRow.ItemArray.SequenceEqual(updatedRow)) yield break;
var length = currentRow.ItemArray.Length;
for(var i = 0; i < length; i++)
{
var currentCol = currentRow[i];
var updatedCol = updatedRow[i];
if (currentCol == null && updatedCol == null) continue;
else if (currentCol == null && updatedCol != null) continue;
else if (currentCol.Equals(updatedCol)) continue;
yield return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedCol)
};
}
}
var updates = currentRow.ItemArray
.Select((o, i) => new { Row = o, Index = i })
.Where(r => (r.Row == null && updatedRow[r.Index] != null)
|| (r.Row != null && updatedRow[r.Index] != null
&& !r.Row.Equals(updatedRow[r.Index])))
.Select(r => new
{
Key = updatedRow.Table.Columns[r.Index].ColumnName,
Value = Convert.ToString(updatedRow[r.Index])
}).ToList();
In general, I consider using array index values in LINQ to be a "code smell", and this is a good example of why: the Where clause, in generating a new sequence of values, destroys the illusion that the Select clause is working on the same collection as before.
A quick hack to get around this right now (though I don't think it is quite yet the right solution), would be to swap your Where and Select clauses, essentially:
if (currentRow.ItemArray.SequenceEqual(updatedRow)) { return; }
var updates = currentRow.ItemArray
.Select((o, i) =>
{
if (o == null && updatedRow[i] == null || o.Equals(updatedRow[i])) { return null; }
else return new AppServices.NotificationData
{
Key = updatedRow.Table.Columns[i].ColumnName,
Value = Convert.ToString(updatedRow[i])
};
}).Where(o => o != null).ToList();
Related
this is a description of my task as it should be done
And that is my code. I would be grateful if someone can help me and explain, why my solution is not good.
public IList<MoneyTransfer> SearchTransfer(string clientAccountNo, decimal? amount,
string phrase, string beneficiaryAccountNo)
{
var listOfMoneyTransfers = new List<MoneyTransfer>();
var result = new List<MoneyTransfer>();
if (clientAccountNo is null)
{
return result;
}
else
{
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (i.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
}
if (listOfMoneyTransfers.Count > 0)
{
foreach (var k in listOfMoneyTransfers)
{
if (k.Amount == amount || k.BeneficiaryAccountNo == beneficiaryAccountNo || k.Title == phrase)
{
result.Add(k);
}
}
}
}
return result;
}
Probably that is mistake during searching by three additional parameters, but I am not sure. Please for a help
private static string CorrectClientAccount =
"12345678901234567890000001";
[Test]
public void SearchTransferBy_BeneficiaryAccount()
{
var service = CreateService();
service.CreateTransfer(CorrectClientAccount, 250m,
"Transfer 1", "12345678901234567890000001");
service.CreateTransfer(CorrectClientAccount, 300m, "Other
2", "12345678901234567890000002");
service.CreateTransfer(CorrectClientAccount, 400m, "Other
3", "12345678901234567890000003");
service.CreateTransfer(CorrectClientAccount, 500m, "Other
4", "12345678901234567890000003");
var result = service.SearchTransfer(CorrectClientAccount,
null, "", "12345678901234567890000003");
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
Assert.IsTrue(result.Any(a => a.ClientAccountNo ==
CorrectClientAccount && a.Amount == 400m && a.Title ==
"Other 3" && a.BeneficiaryAccountNo ==
"12345678901234567890000003"));
Assert.IsTrue(result.Any(a => a.ClientAccountNo ==
CorrectClientAccount && a.Amount == 500m && a.Title ==
"Other 4" && a.BeneficiaryAccountNo ==
"12345678901234567890000003"));
}
That is unit test
This part of the code looks problematic:
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (i.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
}
When looking trough bank accounts and money transfers, you do not check that transfer belongs to a particular account.
This code would add all of the money transfers to the list, if there is a bank account with this clientAccountNo, and not just the transfers belonging to the client.
Most likely, you need to do something like this (I can only guess for variable names I've not been provided with)
(this also assumes that MoneyTransfer doesn't have AccountNo)
var clientBankAccount = BankAccounts.FirstOrDefault(account => account.AccountNo == clientAccountNo);
// be sure to check that account has been found
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (j.BankAccountId == clientBankAccount.Id)
{
listOfMoneyTransfers.Add(j);
}
}
I would first focus on the functional correctness of code and then we can also discuss the the efficiency.
Functional issue: There is no relationship between BankAccounts and TransferHistory list iterations. We are selecting all the transfer histories without filtering if those originate from the clientBankAccount as we are just matching the clinetAccountNumber with outer iteration item(BankAccount i), all the transfers are included if that condition matches. We should have filtered only those Tranfers that originated from the clientBankAccount.
foreach (BankAccount i in BankAccounts.ToList())
{
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if ***(i.AccountNo == clientAccountNo)***
{
listOfMoneyTransfers.Add(j);
}
}
}
There could have been many optimizations like:
Adding precondition checks like:
if (null == amount && string.IsNullOrWhilespace(beneficiaryAccountNo) && string.IsNullOrWhilespace(phrase))
{
return new List<MoneyTransfer>();
}
foreach (MoneyTransfer j in TransfersHistory.ToList())
{
if (j.AccountNo == clientAccountNo)
{
listOfMoneyTransfers.Add(j);
}
}
You don’t really need the outer loop, and instead be checking for j.ClientAccountNo == clientAccountNo, to get the list of transfers which belong to correct account.
foreach (var k in listOfMoneyTransfers)
{
if (k.Amount == amount || k.BeneficiaryAccountNo == beneficiaryAccountNo || k.Title == phrase)
{
result.Add(k);
}
}
This is also an issue, the expression is shortcuited once a condition is met, hence it might result in situations where it returns money transfers which match the amount, but doesn’t match the beneficiary account number or contain the search phrase.
Once a condition is met, the rest are not evaluated.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators#conditional-logical-or-operator-
Consider using Linq where clause and conditional searching. something like
var results = listOfMoneyTransfers
.Where(x => amount.HasValue ? x == amount : true)
.Where(x => string.InNullOrWhiteSpace(phrase) ? x.Title.contains(phrase) : true)
.Where(x => string.InNullOrWhiteSpace(beneficiaryAccountNo) ? x.BeneficiaryAccountNo == beneficiaryAccountNo : true)
The multiple where clauses will be flatted into a single predicate https://github.com/dotnet/runtime/blob/a24364a09d9aea98b545f16689a53bafc6b18c14/src/libraries/System.Linq/src/System/Linq/Utilities.cs#L56
I want to verify that a string does not contain any duplicate characters (from a set of bad characters) in adjacent positions. Previous stack overflow answers on this subject seem to mostly be of the general form:
for(int i = 0; i < testString.Length-1; i++){
if(testString[i] == testString[i+1] && testString[i] == badChar){
//Handle rejection here
}
}
Is it possible to do this kind of verification/validation in LINQ? More generically: is it possible within LINQ to compare the value of each character in a string to the next character in a
testString.Any(c => /*test goes here*/) call?
Anytime you have a class that has Count (or equivalent) property and indexer, you can use Enumerable.Range as base for the LINQ query and perform inside an indexed access similar to the non LINQ code:
bool test = Enumerable.Range(0, testString.Length - 1).Any(i = >
testString[i] == testString[i + 1] && testString[i] == badChar)
You could use Pairwise from moreLINQ library:
if(testString.Pairwise((n, m) => new {n, m}).Any(x => x.n == x.m && x.n == badChar))
// do something
If you want to use pure LINQ you could hack it with Skip/Zip combination:
if(testString.Zip(testString.Skip(1), (n, m) => new {n, m})).Any(x => x.n == x.m && x.n == badChar))
// do something
But both these solutions will be much slower then for loop-based solution, so I'd advice against doing that.
How about the egregious misuse of the aggregate function? I like to think this answer is more of an example of what not to do, even if it is possible. A while and string.indexOf are probably the most appropriate to this problem.
var items = "ab^cdeef##gg";
var badChars = new[] {'^', '#', '~'};
var doesAdjacentDupeExist = false;
var meaninglessAggregate = items.Aggregate((last, current) =>
{
if (last == current && badChars.Contains(last))
{
doesAdjacentDupeExist = true;
};
return current;
});
This is not as clever, but it does work. It trades the setting of an outside variable inside the query (bad), for relying on index and elementAt (not great).
var items = "abcdefffghhijjk";
var badChars = new[] { 'f', 'h' };
var indexCieling = items.Count() - 1;
var badCharIndexes = items.Select((item, index) =>
{
if (index >= indexCieling)
{
return null as int?;
}
else
{
if (item == items.ElementAt(index + 1) && badChars.Contains(item))
{
return index as int?;
}
else
{
return null as int?;
}
}
});
var doesAdjacentDupeExist = badCharIndexes.Any(x => x.HasValue);
i got error as: An unhandled exception of type 'System.IndexOutOfRangeException' occurred in app.exe
Additional information: Index was outside the bounds of the array.
by using code below, i appreciate your help in-advance all.
public string getMissingFields(WebBrowser wb, DataRow dr)
{
string Available2 = "";
Available2 = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt")
.Style.Split(';')
.Where(x => x.Contains("display"))
.ToArray()[0].Split(':')[1];
string FieldsMissing="";
if( Available2 .Contains( "inline" )) {
FieldsMissing = FieldsMissing + "First name missing!" + ", ";
}
return FieldsMissing;
}
You're assumiing that the style will always contain "display", which apparently it does not. Replace your indexer call to offset 0 with a call to FirstOrDefault(), then test for null:
Available2 = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt").Style.Split(';').Where(x => x.Contains("display")).ToArray().FirstOrDefault();
if( null != Available2 )
{
// continue
}
Available2 = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt")
.Style.Split(';')
.Where(x => x.Contains("display"))
.ToArray()[0].Split(':')[1];
Two possible problems:
Either ToArray() does return an empty array, at with point accessing element 0 causes this error. Or it is at the point where you are accessing the element at index 1 - maybe there is no such element, because in the string you're trying to split there is no :? Debug your code or make sure that there is at least one element returned by ToArray() and two elements returned by Split.
You could try this now. This splits your code so that you can easily debug:
var items = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt")
.Style.Split(';')
.Where(x => x.Contains("display"))
.ToArray();
if (items.Count > 0)
{
string[] split = items[0].Split(':');
if (split.Length > 1)
Available2 = split[1];
}
Two possibilities:
ToArray() returning empty array.You are trying to an element which is not exist.
Split(':') returning zero or one element.
Debug your code and find which one is true.
It seems you don't need ToArray.Just use FirstOrDefault, and check returning result whether null or not.If it isn't null call Split and check again to prevent exception.
Available2 = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt")
.Style.Split(';')
.Where(x => x.Contains("display"))
.FirstOrDefault();
if(Available2 != null)
{
var text = Available2.Split(':');
if(text.Length > 1)
{
var result = text[1];
}
}
First, consider that there's no control with id=ContentPlaceHolder1_reqTxt:
var reqTxt = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt");
You have to handle the case that it's null:
if(reqTxt != null)
{
}
Now consider that there's is no style display, then ToArray returns an empty array. You can use FirstOrDefault and check for null gain:
string Available2 = null;
if(reqTxt != null)
{
var firstDisplayStyle = reqTxt.Split(';')
.FirstOrDefault(s => s.Contains("display"));
if(firstDisplayStyle != null)
{
string[] displaySplit = firstDisplayStyle.Split(':');
// now handle the case that there is no colon:
if(displaySplit.Length > 1)
Available2 = displaySplit[1];
}
}
Available2=wb.Document.GetElementById("ContentPlaceHolder1_reqTxt").Style.Split(';').Where(x => x.Contains("display")).ToArray()[0].Split(':')[1];
To find problem, decomposite to:
if (wb == null || wb.Document == null )
return;
var element = wb.Document.GetElementById("ContentPlaceHolder1_reqTxt");
if (element == null || element.Style == null)
return;
var displayItems = element.style.Split(';').Where(x=> x.Contains("display")).FirstOrDefault();
if ( displayItems == null)
return;
var colonItems = displayItems.Split(':');
if ( colonItems.Count() < 2 )
return;
var Available2 = colonItems.Skip(1).First();
All I want to do is declare var place correctly so it is still in scope once I get to the foreach loop. I'm assuming I need to declare it before the if statement for connections. Is this a correct assumption and if so how do I declare it? Thanks!
using (var db = new DataClasses1DataContext())
{
if (connections == "Connections")
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && v.WD_Connect >= 1
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
else
{
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region && ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
}
foreach (var result in place)
....
You can create an array with a single entry whose value you ignore later:
// Note: names *and types* must match the ones you use later on.
var place = new[] { new { locName = "", latitude = 0.0, longitude = 0.0 } };
if (connections = "Connections")
{
// Note: not a variable declaration
place = ...;
}
else
{
place = ...;
}
This works because every use of anonymous types using properties with the same names and types, in the same order, will use the same concrete type.
I think it would be better to make the code only differ in the parts that it needs to though:
var query = v.db.pdx_aparts.Where(v => v.Latitude != null && v.Region == region);
query = connections == "Connections"
? query.Where(v => v.WD_Connect >= 1)
: query.Where(v => v.WD_Connect == null || v.WD_Connect == 0);
var places = query.Select(v => new
{
locName = v.Apartment_complex
.Trim()
.Replace("\"", ""),
latitude = v.Latitude,
longitude = v.Longitude
})
.Distinct()
.ToArray();
Here it's much easier to tell that the only part which depends on the connections value is the section of the query which deals with WD_Connect.
You could convert the if to a ?:.
var place = connections == "Connections" ? monsterQuery1 : monsterQuery2;
I do not think this is a good solution because your queries are too big (unreadable).
It would be much better if you introduced a named class that you use instead of the anonymous type. R# does that for you in a "light bulb menu" refactoring.
you could just use the 1 query since they are pretty much the same, and just add the extra condition in the where clause
var place = (from v in db.pdx_aparts
where v.Latitude != null && v.Region == region
&& connections == "Connections"
? v.WD_Connect >= 1
: ((v.WD_Connect == null) || (v.WD_Connect == 0))
select new
{
locName = v.Apartment_complex.Trim().Replace(#"""", ""),
latitude = v.Latitude,
longitude = v.Longitude
}).Distinct().ToArray();
foreach (var result in place)
....
Below my code. It returns exception "InvalidCastException". And main question is - why?What is wrong?
Error text:
Unable to cast object of type
'WhereSelectListIterator`2[Monopolowy_beta.Gracz,Monopolowy_beta.Gracz]'
to type 'Monopolowy_beta.Gracz'.
namespace Monopolowy_beta
{
class Program
{
static void Main(string[] args)
{
List<Gracz> lista = new List<Gracz> { };
Gracz g1 = new Gracz();
Gracz g2 = new Gracz();
Gracz g3 = new Gracz();
g2.Id = 3;
lista.Add(g1);
lista.Add(g2);
lista.Add(g3);
g1 = GraczeTools.UstawAktywnegoGracza(lista, 3);
Console.ReadKey();
}
}
}
Error in these lines:
var docelowy = from item in listagraczy where (item.Id==ID && item.czyAktywny == true) select listagraczy[listagraczy.IndexOf(item) + 1];
gracz = (Gracz)docelowy;
namespace Monopolowy_beta
{
static class GraczeTools
{
public static Gracz UstawAktywnegoGracza(List<Gracz> listagraczy, int ID)
{
Gracz gracz = new Gracz();
if (ID == 4){
var docelowy = from item in listagraczy where (item.czyAktywny == true && item.Id == 3) select listagraczy[1];
gracz = (Gracz)docelowy;
}
if (ID != 4){
var docelowy = from item in listagraczy where (item.Id==ID && item.czyAktywny == true) select listagraczy[listagraczy.IndexOf(item) + 1];
gracz = (Gracz)docelowy;
}
return gracz;
}
}
}
var docelowy = from item in listagraczy
where (item.czyAktywny == true && item.Id == 3)
select listagraczy[1];
Let's examine this query. It finds all items which satisfy condition (yes, it will return sequence, not single item) and for each such item, it returns.. second element of listagraczy list. Yes, you don't have items, which matched your condition.
I think you should select item instead (this a range variable of your query), and apply FirstOrDefault to result, because by default query will return IEnumerable<Gracz> result.
var docelowy = (from item in listagraczy
where (item.czyAktywny == true && item.Id == 3)
select item).FirstOrDefault();
Which is better to write with fluent API:
var docelowy = listagraczy.FirstOrDefault(item => item.czyAktywny && item.Id == 3);
Also you can use boolean values directly in conditions (i.e. item.czyAktywny instead of item.czyAktywny == true).
After little refactoring your method should look like
public static Gracz UstawAktywnegoGracza(List<Gracz> listagraczy, int ID)
{
return listagraczy
.FirstOrDefault(item => item.Id == 3 && (ID != 4 || item.czyAktywny));
}
How it works:
You have two conditional blocks in your method if (ID == 4) and if (ID != 4) (which is actually if ... else. Difference is that you are filtering sequence by one more condition in first case - item.czyAktywny should be true. In second case this property does not matter. So, you can add one filtering condition instead (ID != 4 || item.czyAktywny) - czyAktywny will be verified only if ID equal to 4. Also you don't need to create new Gracz object in your method, because you anyway return one from passed list.
Instead of gracz = (Gracz)docelowy;, use gracz = docelowy.FirstOrDefault();
Your select statement is returning an IEnumerable<Gracz>. So, when you attempt to cast it to Gracz directly, the computer doesn't know how to do that and throws the error you're seeing.
There are a number of ways you can handle this situation, but the simplest would be to simply add FirstOrDefault to your invocation, giving you
gracz = docelowy.FirstOrDefault();
instead of what you currently have. By the way, you'll need to add this to your other if statement too - it has the same problem.