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 its row#
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 compares by identity too — the same instance — and a record by its contents. The entity's identity is its row, which is why the same row read twice is still one value.
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 — a
classby its instance, arecordby its members - 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