dotnet / dotnet/efcore

No backing field could be found for property 'Email.EmployeeId' and the property does not have a getter

Open
#37,226 5 comments 0 reactions 0 assignees View on GitHub
area-owned-entities area-query customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

The following error happens during data seeding when querying the entity by the value type property.

```
No backing field could be found for property 'Email.EmployeeId' and the property does not have a getter for value type
```

The Employee Model:

```
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CleanHr.Domain.Aggregates.DepartmentAggregate;
using CleanHr.Domain.Exceptions;
using CleanHr.Domain.Primitives;
using CleanHr.Domain.ValueObjects;

namespace CleanHr.Domain.Aggregates.EmployeeAggregate;

public sealed class Employee : AggregateRoot
{
internal Employee(
IDepartmentRepository departmentRepository,
IEmployeeRepository employeeRepository,
EmployeeName name,
Guid departmentId,
DateOfBirth dateOfBirth,
Email email,
PhoneNumber phoneNumber)
{
Id = Guid.NewGuid();
SetName(name);
SetDepartmentId(departmentRepository, departmentId);
SetDateOfBirth(dateOfBirth);
SetEmail(employeeRepository, email);
SetPhoneNumber(employeeRepository, phoneNumber);
CreatedAtUtc = DateTime.UtcNow;
}

// This is needed for EF core query mapping and serialization.
[JsonConstructor]
private Employee()
{
}

public EmployeeName Name { get; private set; }

public Guid DepartmentId { get; private set; }

public DateOfBirth DateOfBirth { get; private set; }

public Email Email { get; private set; }

public PhoneNumber PhoneNumber { get; private set; }

public bool IsActive { get; set; }

public DateTime CreatedAtUtc { get; }

public DateTime? LastModifiedAtUtc { get; private set; }

// Navigation Properties
public Department Department { get; private set; }

// Public methods
public void SetName(EmployeeName name)
{
Name = name ?? throw new DomainValidationException("The name cannot be null.");
}

public void SetDateOfBirth(DateOfBirth dateOfBirth)
{
DateOfBirth = dateOfBirth ?? throw new DomainValidationException("The dateOfBirth cannot be null.");
}

public async Task SetDepartmentAsync(IDepartmentRepository repository, Guid departmentId)
{
ArgumentNullException.ThrowIfNull(repository);

if (departmentId == Guid.Empty)
{
throw new DomainValidationException("The departmentId cannot be empty guid.");
}

if (DepartmentId != Guid.Empty && DepartmentId.Equals(departmentId))
{
return;
}

bool isDepartmentExistent = await repository.ExistsAsync(d => d.Id == departmentId);

if (isDepartmentExistent == false)
{
throw new DomainValidationException($"The Department does not exist with the id value: {departmentId}");
}

DepartmentId = departmentId;
}

public async Task SetEmailAsync(IEmployeeRepository repository, Email email)
{
ArgumentNullException.ThrowIfNull(repository);

if (email == null)
{
throw new DomainValidationException("The email cannot be null.");
}

if (Email != null && Email.Value.Equals(email.Value, StringComparison.OrdinalIgnoreCase))
{
return;
}

bool isPhoneNumberExistent = await repository.ExistsAsync(d => d.Email == email);

if (isPhoneNumberExistent)
{
throw new DomainValidationException("An employee already exists with the provided email.");
}

Email = email;
}

public async Task SetPhoneNumberAsync(IEmployeeRepository repository, PhoneNumber phoneNumber)
{
ArgumentNullException.ThrowIfNull(repository);

if (phoneNumber == null)
{
throw new DomainValidationException("The phoneNumber cannot be null.");
}

if (PhoneNumber != null && PhoneNumber.Value.Equals(phoneNumber.Value, StringComparison.OrdinalIgnoreCase))
{
return;
}

bool isPhoneNumberExistent = await repository.ExistsAsync(d => d.PhoneNumber == phoneNumber);

if (isPhoneNumberExistent)
{
throw new DomainValidationException("An employee already exists with the provided phone number.");
}

PhoneNumber = phoneNumber;
}

private void SetDepartmentId(IDepartmentRepository repository, Guid departmentId)
{
SetDepartmentAsync(repository, departmentId).GetAwaiter().GetResult();
}

private void SetEmail(IEmployeeRepository repository, Email email)
{
SetEmailAsync(repository, email).GetAwaiter().GetResult();
}

private void SetPhoneNumber(IEmployeeRepository employeeRepository, PhoneNumber phoneNumber)
{
SetPhoneNumberAsync(employeeRepository, phoneNumber).GetAwaiter().GetResult();
}
}

```

The Email Value Type

```
using System.Collections.Generic;
using System.Text.RegularExpressions;
using CleanHr.Domain.Exceptions;
using CleanHr.Domain.Primitives;

namespace CleanHr.Domain.ValueObjects;

public sealed class Email : ValueObject
{
private const int _maxLength = 50;

public Email(string value)
{
SetValue(value);
}

public string Value { get; private set; }

protected override IEnumerable GetEqualityComponents()
{
yield return Value;
}

private void SetValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new DomainValidationException("The Email cannot be null or empty.");
}

if (value.Length > _maxLength)
{
throw new DomainValidationException($"The Email length must be less than {_maxLength + 1} characters.");
}

Regex emailRegex = new(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");

Match match = emailRegex.Match(value);

if (match.Success == false)
{
throw new DomainValidationException("The Email value is not a valid email.");
}

Value = value;
}
}

```

The Employee Model Configuration:

```
using CleanHr.Domain.Aggregates.EmployeeAggregate;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace CleanHr.Persistence.RelationalDB.EntityConfigurations.EmployeeAggregate;

public class EmployeeConfiguration : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder builder)
{
builder.ToTable("Employees");
builder.HasKey(emp => emp.Id);

builder.OwnsOne(emp => emp.Name).Property(n => n.FirstName)
.HasColumnName("FirstName").HasMaxLength(50).IsRequired();

builder.OwnsOne(emp => emp.Name).Property(n => n.LastName)
.HasColumnName("LastName").HasMaxLength(50).IsRequired();

builder.Navigation(emp => emp.Name).IsRequired();

builder.HasOne(emp => emp.Department).WithMany().HasForeignKey(emp => emp.DepartmentId).IsRequired();

builder.OwnsOne(emp => emp.DateOfBirth)
.Property(d => d.Value).HasColumnName("DateOfBirth").HasColumnType("date");
builder.Navigation(emp => emp.DateOfBirth).IsRequired();

builder.OwnsOne(emp => emp.Email)
.Property(e => e.Value).HasColumnName("Email").HasMaxLength(50).IsRequired();
builder.Navigation(emp => emp.Email).IsRequired();

builder.OwnsOne(emp => emp.PhoneNumber)
.Property(p => p.Value).HasColumnName("PhoneNumber").HasMaxLength(15).IsRequired();
builder.Navigation(emp => emp.PhoneNumber).IsRequired();
}
}

```

The line causes problem during the data seeding

```
bool isPhoneNumberExistent = await repository.ExistsAsync(d => d.Email == email);
```

### Your code

```csharp
Code provided in the description.
```

### Stack traces

```text
System.InvalidOperationException: No backing field could be found for property 'Email.EmployeeId' and the property does not have a getter.
at Microsoft.EntityFrameworkCore.Metadata.IPropertyBase.GetMemberInfo(Boolean forMaterialization, Boolean forSet)
at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertyGetterFactory.GetMemberInfo(IPropertyBase propertyBase)
at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrAccessorFactory`1.CreateBase(IPropertyBase propertyBase)
at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertyGetterFactory.Create(IPropertyBase property)
at Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase.<>c.b__51_0(RuntimePropertyBase property)
at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
at Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase.Microsoft.EntityFrameworkCore.Metadata.IPropertyBase.GetGetter()
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.ParameterValueExtractor[T](QueryContext context, String baseParameterName, List`1 complexPropertyChain, IProperty property)
at lambda_method104(Closure, QueryContext)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AnyAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
```

### Verbose output

```text

```

### EF Core version

10.0.0

### Database provider

Microsoft SQL server

### Target framework

.NET 10.0

### Operating system

Mac OS 26.2

### IDE

VSCode latest

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.