Matjaž
Matjaž

Reputation: 2115

EF Core base entity class inheritance and readonly properties

Is it possible to do something like this:

public abstract class BaseEntity : IEntity
{
    private Guid _id;
    private DateTime _createdOn;
    private DateTime _modifiedOn;
    private byte[] _timestamp;

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id => _id;

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public DateTime CreatedOn => _createdOn;

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime ModifiedOn => _modifiedOn;

    [Timestamp]
    public byte[] Timestamp => _timestamp;
}

This is base class, all entities should inherit from this class. Also this properties should be readonly (I'm using backing properties), because EF need to take care of them.

public class Category : BaseEntity
{
    [Required]
    public string Name { get; set; }

    public string Description { get; set; }
}

And OnModelCreating is empty. When I run migrations, I get message :

'The entity type 'Project.Database.Models.Category' requires a primary key to be defined.

Upvotes: 1

Views: 4331

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205529

This seems to be a bug, because it works if you provide public, internal or protected (but not private or none) setter for the Id property, or if you specify the key of the derived entity explicitly via Fluent API

modelBuilder.Entity<Category>().HasKey(e => e.Id);

which of course defeats the whole purpose of using the base class property.

You might consider reporting it to EF Core GitHub.

Upvotes: 1

Pat
Pat

Reputation: 236

The problem is that your BaseEntity.Id property is read only.

Your Id property is implemented as an expression-bodied member (the => syntax), which is merely syntactic sugar for a get-only property.

So, your code:

public Guid Id => _id;

is equivalent to:

public Guid Id { get { return _id; } }

EF must be able to set the values of mapped properties. You'll have to use traditional syntax to implement the property, and you'll have to provide a setter. If your context is in the same assembly as your model, you could mark the setter as internal so that the property is read only outside the assembly. If your context is in a different assembly, you could still mark the setter as internal, then use the InternalsVisibleTo attribute to make internals visible to the assembly containing your context.

Upvotes: 2

Related Questions