9

I am trying to create an entity class which will expose a related collection through a readonly property, like this:

public class MyEntity: Entity
{
    public int Id{ get; private set; }
    private IList<RelatedEntity> _relatedEntities = new List<RelatedEntity>();
    public IReadOnlyList<RelatedEntity> RelatedEntities => _relatedEntities.ToList().AsReadOnly();
}

The builder class looks like this:

public void Configure(EntityTypeBuilder<MyEntity> builder)
{
    builder.HasKey(x=>x.Id);
    builder.Property<IReadOnlyList<RelatedEntity>>("RelatedEntities")
        .HasField("_relatedEntities ")
        .UsePropertyAccessMode(PropertyAccessMode.Field);
}

It builds but crashes at runtime with the exception:

InvalidOperationException: The specified field '_relatedEntities' of type 'IList' cannot be used for the property 'MyEntity.RelatedEntities ' of type 'IReadOnlyList'. Only backing fields of types that are assignable from the property type can be used.

Could you provide a working example how to deal with this issue ?

vanpersil
  • 764
  • 1
  • 8
  • 26
  • I don't have the means to test it right now, but knowing how EF works, perhaps just try changing your `_relatedEntities` field to `protected`? – Steve Danner Sep 21 '19 at 17:46
  • No - I am not using proxies extension (they need to be added explicitly with nuget package). Without this, the properties, fields and constructors can be private, as they are handled internally via reflection. – vanpersil Sep 21 '19 at 17:50

4 Answers4

8

I ckeck this and it worked:

private readonly List<RelatedEntity> _relatedEntitys;
public IReadOnlyCollection<RelatedEntity> RelatedEntitys => _relatedEntitys;

And configuration must be like below:

    builder.HasMany(x => x.RelatedEntitys)
        .WithOne()
        .IsRequired()
        .HasForeignKey(x => x.RelatedEntityId)
        .OnDelete(DeleteBehavior.Cascade);

    builder.Metadata
        .FindNavigation("RelatedEntitys")
        .SetPropertyAccessMode(PropertyAccessMode.Field);
Mohammad Niazmand
  • 1,509
  • 9
  • 13
5

EF core requires you to use concrete types for backing fields. You need to change your code to:

private readonly List<RelatedEntity> _relatedEntities = new List<RelatedEntity>();
public IReadOnlyList<RelatedEntity> RelatedEntities => _relatedEntities.ToList();
Piotr K.
  • 106
  • 1
  • 4
1

The error message is loud and clear:

IList is not assignable to IReadOnlyList

Changing the property type to the same type as the backing field will do the trick.

Update:

Because IEnumerable<T> is read-only by default, this would be your best bet I believe.

    public class MyEntity: Entity
    {
        public int Id { get; private set; }

        private readonly List<RelatedEntity> _relatedEntities = _collection.ToList().AsReadOnly();

        public IEnumerable<RelatedEntity> RelatedEntities => _relatedEntities;
    }

Update your fluent API as follows:

    builder.HasKey(x=>x.Id);
    builder.Metadata.FindNavigation("RelatedEntities")
        .UsePropertyAccessMode(PropertyAccessMode.Field);
Community
  • 1
  • 1
Dennis VW
  • 2,977
  • 1
  • 15
  • 36
  • I can see one problem: IList exposess Add method - which I wanted to avoid. I don't want the client code to have an illusion that something can be added to the collection. The second problem for this approach (beacuse I tried it) is that I get an exception: The property 'Entity.RelatedEntity' is of type 'IList' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. – vanpersil Sep 21 '19 at 18:04
  • I updated my answer @Marcin. Using an IEnumerable will not expose add since it's readonly by default. – Dennis VW Sep 21 '19 at 18:28
  • Yes - this would work, but the exception stays the same: The property 'MyEntity.Rooms' is of type 'IEnumerable' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. – vanpersil Sep 21 '19 at 18:33
  • (and sorry for confusing - there is no _collection field or property in the class - I've corrected the question) – vanpersil Sep 21 '19 at 18:35
  • Did you change your fluent api to reflect a List instead of IReadOnlyList ? – Dennis VW Sep 21 '19 at 18:38
  • Yes - of course. Event though I think it is not necessary due to the conventions (https://learn.microsoft.com/en-us/ef/core/modeling/backing-field). But I changed. – vanpersil Sep 21 '19 at 18:40
  • Use FindNavigation to find related entities. Updated my answer @Marcin – Dennis VW Sep 21 '19 at 18:45
  • Are you sure it'll compile? The semicolon after ("RelatedEntities"); is clearly a mistake, but is there HasField method available? – vanpersil Sep 21 '19 at 18:47
  • Sorry, you're right. There isn't any. But as long as you keep to naming conventions EF will be able to find navigation property. (that might mean ditching the underscore) – Dennis VW Sep 21 '19 at 18:53
-1

For EF Core 2, I believe the backing field must be of type HashSet<T> to be correctly recognized by the framework, so this should work:

public class MyEntity {
  private HashSet<RelatedEntity> _relatedEntities = new HashSet<RelatedEntity>();

  public IReadOnlyCollection<RelatedEntity> RelatedEntities => _relatedEntities;
}

See also https://entityframeworkcore.com/knowledge-base/54840443/ef-core-one-to-many-relationships--icollection-or-hashset-

Oldrich Dlouhy
  • 597
  • 6
  • 6