Для свойства типа сущности не найдено поддерживающее поле, и у свойства нет геттера. - PullRequest
1 голос
/ 09 мая 2020

Я получаю исключение

System.InvalidOperationException : No backing field could be found for property 'ApartmentId' of entity type 'Address' and the property does not have a getter.

Это мой Apartment класс:

public class Apartment
{
    public Apartment(Address address)
    {
        Address = address;
    }

    private Apartment()
    {
    }

    public int Id { get; private set; }
    public Address Address { get; private set; }
}

Это мой Address класс объекта value:

public class Address : IEquatable<Address>
{
    private Address()
    {
    }

    public Address(string streetNumber, string streetName, string city, string state, string zipCode)
    {
        StreetNumber = streetNumber;
        StreetName = streetName;
        City = city;
        State = state;
        ZipCode = zipCode;
    }

    public string StreetNumber { get; private set; }
    public string StreetName { get; private set; }
    public string City { get; private set; }
    public string State { get; private set; }
    public string ZipCode { get; private set; }

    public bool Equals(Address other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }

        if (ReferenceEquals(this, other))
        {
            return true;
        }

        return String.Equals(StreetNumber, other.StreetNumber, StringComparison.OrdinalIgnoreCase) &&
               String.Equals(StreetName, other.StreetName, StringComparison.OrdinalIgnoreCase) &&
               String.Equals(City, other.City, StringComparison.OrdinalIgnoreCase) &&
               String.Equals(State, other.State, StringComparison.OrdinalIgnoreCase) &&
               String.Equals(ZipCode, other.ZipCode, StringComparison.OrdinalIgnoreCase);
    }
}

В моей конфигурации объекта я использую builder.OwnsOne(a => a.Address);. В моем репозитории я делаю этот вызов:

    public async Task<Apartment> GetByAddressAsync(Address address)
    {
        return await _context.Apartments.FirstOrDefaultAsync(a => a.Address.Equals(address));
    }

И это вызывает исключение, указанное выше. Любые идеи? Я не знаю, почему в моем объекте значения Address указано значение «ApartmentId».

1 Ответ

2 голосов
/ 09 мая 2020

Сообщение об исключении, конечно, смешно и не имеет ничего общего с реальной проблемой, которая является выражением

a => a.Address.Equals(address)

Оно является частью дерева выражений IQueryable, поэтому EF Core пытается преобразовать его в SQL.

Объектно-ориентированные функции, такие как инкапсуляция, плохо сочетаются с преобразованием выражений, которое основано на видимости и знаниях. EF Core не является декомпилятором, он не видит реализацию вашего метода Equals. Что обычно они делают с неизвестными методами, так это генерируют исключение времени выполнения, предлагающее вам либо использовать переводимую конструкцию, либо явно переключиться на оценку клиента.

Однако типы сущностей имеют особую обработку. EF Core пытается поддерживать преобразование сравнения на равенство (==, '! = , Equals`), неявно преобразовывая его в сравнение PK (первичный ключ).

И здесь возникает проблема с вашим собственным объектом тип. Обратите внимание, что принадлежащие типы сущностей по-прежнему являются типами entity , но ссылочные принадлежащие типы, такие как ваш Address, не имеют собственного PK, отсюда и исключение.

Конечно, то, что они делают, является ошибкой, но даже если они "исправят" это, исправление будет просто другим исключением времени выполнения.

Решение, конечно, состоит в том, чтобы не использовать метод Equals, а явное сравнение членов, например

a => a.Address.StreetNumber.ToUpper() == address.StreetNumber.ToUpper()
    && a.Address.StreetName.ToUpper() == address.StreetName.ToUpper()
    && a.Address.City.ToUpper() == address.City.ToUpper()
    && a.Address.State.ToUpper() == address.State.ToUpper()
    && a.Address.ZipCode.ToUpper() == address.ZipCode.ToUpper()

Обратите внимание, что сравнение строк контролируется базой данных, поэтому явное ToUpper() необходимо, если вам нужно принудительно использовать сравнение без учета регистра.

Теперь я знаю, что это дублирование кода и нарушение инкапсуляции, но это единственный способ (кроме случаев, когда вы используете какую-то стороннюю библиотеку, например Лямбда-инъекция от NeinLinq.EntityFrameworkCore , DelegateDecompiler et c.), Чтобы получить фильтрацию на стороне сервера.

Потому что фильтрация на стороне клиента после чтения всей таблицы, например

_context.Apartments.AsEnumerable().FirstOrDefault(a => a.Address.Equals(address))

, будет убийца производительности (и является причиной удаления неявной оценки клиента в EF Core 3.0+), не считая отсутствия естественной поддержки async (требуется дополнительный пакет, который, в свою очередь, вызывает проблемы с EF Core DbSet s - см. Преобразование запросов EF Core из 2.2 в 3.0 - asyn c await ).

...