sensei
sensei

Reputation: 7592

Entity Framework generic entity inheritance id error

Part 2 problem, which continues from here: EntityFramework Core - Update record fails with DbUpdateConcurrencyException

Error:

The derived type 'BinaryFile' cannot have KeyAttribute on property 'Id' since primary key can only be declared on the root type.

I am trying to make inheritance for my entities as much as possible, so I remove duplication as much as possible.

My inheritance structure:

public interface IEntityMinimum
{
    bool IsDeleted { get; set; }
    byte[] Version { get; set; }
    string CreatedBy { get; set; }
}

public class EntityMinimum : IEntityMinimum
{
    public bool IsDeleted { get; set; }
    [Timestamp]
    public byte[] Version { get; set; }
    public string CreatedBy { get; set; }
}

public interface IEntity : IEntityMinimum
{
    object Id { get; set; }
    DateTime CreatedDate { get; set; }
    DateTime? ModifiedDate { get; set; }
    string ModifiedBy { get; set; }
}

public interface IEntity<T> : IEntity
{
    new T Id { get; set; }
}

public abstract class Entity<T> : EntityMinimum, IEntity<T> 
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public T Id { get; set; }

    object IEntity.Id
    {
        get => Id;
        set => throw new NotImplementedException();
    }

    private DateTime? _createdDate;
    [DataType(DataType.DateTime)]
    public DateTime CreatedDate
    {
        get => _createdDate ?? DateTime.UtcNow;
        set => _createdDate = value;
    }

    [DataType(DataType.DateTime)]
    public DateTime? ModifiedDate { get; set; }

    public string ModifiedBy { get; set; }
}

public class EntityMaximum : Entity<int>
{
    public bool IsActive { get; set; }
}

public class BinaryFile : EntityMaximum
{
    public string Name { get; set; }
    public string UniqueName { get; set; }
    public Guid UniqueId { get; set; }
    public byte[] Content { get; set; }

    public virtual ICollection<Campaign> Campaigns { get; set; }
}

I get this error when I use Fluent API to disable isConcurrencyToken on the Version field for the EntityMinimum class like this:

    // https://stackoverflow.com/questions/44009020/entity-framework-isrowversion-without-concurrency-check
    builder.Entity<EntityMinimum>().Property(x => x.Version).IsRowVersion().IsConcurrencyToken(false);

This is required because I had another issue if I do not disable isConcurrencyToken on the Version field:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

If I remove fluent api configuration, it works, but doesn't update because of Version field which has [TimeStamp] attribute.

I have this [TimeStamp] Version field in EntityMinimum to append Version to every table, so I can use TimeStamp for synchronisation purposes between mobile and web data.

Am I doing this structure correctly, or should I get rid of [TimeStamp] byte[] Version and just use string Version and save DateTime ticks into it for synchronisation purpose?

Upvotes: 1

Views: 1310

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205829

The problem is that the call

builder.Entity<EntityMinimum>()

marks the EntityMinimum class as entity (part of the EF Core inheritance strategy), while as I understand, you use the base class hierarchy just for implementation purposes.

Instead, you could use the EF Core model metadata services to turn off IsConcurrencyToken for the Version property of any real entity derived from EntityMinimum like this:

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    if (typeof(EntityMinimum).IsAssignableFrom(entityType.ClrType))
        entityType.FindProperty("Version").IsConcurrencyToken = false;
}

Upvotes: 3

Related Questions