Ответ
Сравнение сущностей в EF Core имеет нюансы из-за механизма отслеживания изменений (change tracking). Вот основные подходы:
1. Сравнение по первичному ключу (Id) — основной способ для сущностей Сущность определяется своим идентификатором, а не значениями полей. Это согласуется с концепцией Entity в DDD.
public class Product : IEquatable<Product>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(Product other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
// Сравниваем только Id, даже если сущности не отслеживаются
return Id != 0 && Id == other.Id;
}
public override bool Equals(object obj) => Equals(obj as Product);
public override int GetHashCode() => Id.GetHashCode();
}
// Использование
var product1 = await context.Products.FindAsync(1);
var product2 = new Product { Id = 1 };
bool areEqual = product1.Equals(product2); // true, т.к. Id совпадают
2. Учет состояния отслеживания (DbContext) EF Core считает две ссылки равными, если они представляют одну и ту же отслеживаемую сущность.
var p1 = await context.Products.FirstAsync(p => p.Id == 1);
var p2 = await context.Products.FirstAsync(p => p.Id == 1);
// p1 == p2 будет true, если запрос использовал отслеживание (AsNoTracking не применялся)
// и оба объекта получены в одном экземпляре DbContext.
3. Сравнение объектов-значений (Value Objects) Для вложенных объектов, которые определяются всеми своими полями, нужно сравнивать каждое свойство.
public record Address(string Street, string City, string ZipCode); // Используем record для автоматического сравнения
// Или вручную:
public class Address : IEquatable<Address>
{
public string Street { get; init; }
...
public bool Equals(Address other) => other != null &&
Street == other.Street &&
City == other.City &&
ZipCode == other.ZipCode;
public override int GetHashCode() => HashCode.Combine(Street, City, ZipCode);
}
Ключевое правило: Для сущностей сравнивайте по Id. Для объектов-значений сравнивайте по всем значимым полям. Всегда переопределяйте GetHashCode вместе с Equals.