Start here. This is the direct spoken answer to practice first.
Why this question matters
An upsert is only race-safe when the database enforces the identity being matched. An application-level check followed by an insert leaves a window where two callers can both decide the row is missing.
I first define the logical identity, such as (TenantId, ExternalOrderId), and enforce it with a unique constraint. Then I use a database-supported atomic upsert or attempt the insert and handle a duplicate-key result by loading or updating the existing row. I do not rely on AnyAsync followed by Add, because two requests can pass that check concurrently. The repeated operation must resolve to one logical record.