# Comparing entity rows

> `==` on two entity references compares them by ROW IDENTITY — not by object reference, and not field by field. Two references to the same row are equal however you fetched them, so `list.Contains(row)`, `list.IndexOf(row)` and `list.Remove(row)` all find the row you mean. You never have to compare `.Id` yourself.

<!-- id: entity-equality · area: entity · stability: stable · html: https://osysharp.com/reference/entity/equality/ -->

## Summary        {#summary}
Two references to the same row **are** the same row, whichever query each came from:

```osy title="two reads of one row, compared directly" test app=entity-equality
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      {#signature}
```osy syntax
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 nothing
```

## Description    {#description}

### An entity compares by identity, a class by value   {#why}
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.

```osy title="identical columns, still two rows" test app=entity-equality
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](https://osysharp.com/reference/class/equality/) 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   {#collections}
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**:

```osy title="asking a list about a row you already hold" test app=entity-equality
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`](https://osysharp.com/reference/ui/reactivity/), 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   {#by-id}
This is the same question, asked the long way round:

```osy title="the two spellings, and they agree" test app=entity-equality
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`](https://osysharp.com/reference/query/in-memory-linq/) 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       {#see-also}
- [entity](https://osysharp.com/reference/entity/declaration/) — what a row is, and how you read one back
- [Comparing classes](https://osysharp.com/reference/class/equality/) — why a `class` compares by its fields instead
- [LINQ over a local list](https://osysharp.com/reference/query/in-memory-linq/) — `IndexOf`, `FindIndex` and the rest of the list verbs
- [Child collections (navigating a relation)](https://osysharp.com/reference/query/collections/) — a parent's child collection, which holds rows the same way
