Summary#
Two references to the same row are the same row, whichever query each came from:
entity Contact {
[Required] string Name;
string Email;
}
bool OneRowTwoReads(string name) {
var a = Contact.Single(c => c.Name == name);
var b = Contact.Where(c => c.Name == name).First();
return a == b; // true — two reads, one row
}a and b were fetched by two separate queries and are still equal. You do not have to write a.Id == b.Id.
Signature#
a == b // true when both references are the SAME ROW
a != b // the negation
a == null // no row — an unset reference, or a lookup that found nothingDescription#
An entity compares by identity, a class by value#
An entity is a row in a table. It has an identity of its own, that identity is what the platform stores, and it
is what == asks about. So the rule cuts both ways:
- The same row read twice is equal to itself — through two queries, in two orders, on the server or on the client. Nothing about how you got hold of it changes the answer.
- Two different rows are never equal, however identical their columns. Two rows are two rows.
bool TwinsAreOneRow() {
var twins = Contact.Where(c => c.Name == "Twin").OrderBy(c => c.Id).ToList();
return twins[0] == twins[1]; // false — same name, same email, two rows
}A class has no identity to compare, so its contents are the answer instead. That is the only difference between the two rules, and it follows from what each thing is.
Contains, IndexOf and Remove all find the row#
Every list operation that takes an element compares with the same ==, so over a list of rows each of them is asking
"is this the same row?" — and each of them gets it right, including when the list came from one query and the row
from another:
string Positions(Contact one) {
var ordered = Contact.OrderBy(c => c.Name).ToList();
return "at=" + ordered.IndexOf(one) // its position, or -1
+ " has=" + ordered.Contains(one) // is it in this list at all
+ " gone=" + ordered.Remove(one) // take that row out of the list
+ " left=" + ordered.Count;
}one was never read by the query that built ordered, and all three still identify it. The same holds on the client:
a row a screen is holding — off a live var, or handed to an action by a foreach — is the same
row the list holds, and these three operations say so.
FindIndex over .Id is not wrong, only longer#
This is the same question, asked the long way round:
bool BothWaysAgree(Contact one) {
var ordered = Contact.OrderBy(c => c.Name).ToList();
return ordered.FindIndex(c => c.Id == one.Id) == ordered.IndexOf(one); // always true
}If you have written the FindIndex form, nothing is broken — it returns the same index, and it is a perfectly
readable thing to have written. It is simply not needed: IndexOf already compares by identity, so the lambda is
restating the rule the language applies anyway.
Reach for FindIndex when you genuinely cannot hold the element — you have an id off a URL,
a name typed by a user, or any other description of the row rather than the row. That is what it is for.
See also#
- entity — what a row is, and how you read one back
- Comparing classes — why a
classcompares by its fields instead - LINQ over a local list —
IndexOf,FindIndexand the rest of the list verbs - Child collections (navigating a relation) — a parent's child collection, which holds rows the same way