Marlon
Marlon

Reputation: 20312

No backing field could be found for property of entity type and the property does not have a getter

I am getting an exception

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

This is my Apartment class:

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

    private Apartment()
    {
    }

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

This is my Address value object class:

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);
    }
}

In my entity configuration I use builder.OwnsOne(a => a.Address);. In my repository I make this call:

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

And it produces the exception above. Any ideas? I don't know why it says I have an 'ApartmentId' in my Address value object.

Upvotes: 5

Views: 3246

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205829

Exception message of course is ridiculous, and has nothing in common with the actual problem, which is the expression

a => a.Address.Equals(address)

It is part of a IQueryable expression tree, hence EF Core is trying to translate it to SQL.

Object-oriented features like encapsulation don't play well with expression translation, which is based on visibility and knowledge. EF Core is not a decompiler, it cannot see the implementation of your Equals method. What usually they do with unknown methods is to throw runtime exception requesting you to either use translatable construct or explicitly switching to client evaluation.

However entity types have special treatment. EF Core is trying to support equality comparisons (==, '!=,Equals`) translation by implicitly converting it to PK (primary key) comparison.

And here comes the issue with your owned entity type. Note that owned entity types are still entity types, but reference owned types like your Address have no own PK, hence the exception.

Of course what they do is a bug, but even they "fix" it, the fix would be just different runtime exception.

The solution is of course to not use the Equals method, but explicit member comparison, e.g.

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()

Note that string comparison is controlled by the database, so explicit ToUpper() is needed if you need to enforce case insensitive comparison.

Now I know this is a code duplication and breaks encapsulation, but this is the only way (except if you utilize some 3rd party library like Lambda injection by NeinLinq.EntityFrameworkCore, DelegateDecompiler etc.) to get server side filtering.

Because client side filtering after reading the whole table like

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

would be a performance killer (and is the reason for removing implicit client evaluation in EF Core 3.0+), not to count the lack of natural async support (requires additional package, which in turn causes issues with EF Core DbSets - see Converting EF Core queries from 2.2 to 3.0 - async await).

Upvotes: 10

Related Questions