简体   繁体   中英

Entity Framework Core not supporting generic abstract entities with many to many relationships

I have faced a strange problem witch EF Core 1.1. I m trying to build application where some entities can be tagged, thus I've created an abstract generic class for the relation table list. The problem is that, it seems like EF do not support to have a generic abstract classes which FK (Id property works).

Here are models:

public abstract class TaggedEntityBase<T> : EntityBase
{
    public ICollection<T> EntityTags { get; set; }
    public List<Tag> Tags { get { return EntityTags?.Select(x => x.Tag).ToList(); } }
}

public class AddressTag
{
    public long TagId { get; set; }
    public Tag Tag { get; set; }
    public long EntityId { get; set; }
    public Address Entity { get; set; }
}

public class Address : TaggedEntityBase<AddressTag>
{
    public string Street { get; set; }
    public string City { get; set; }
}
public class Tag : EntityBase
{
    public string Name { get; set; }
    public virtual ICollection<AddressTag> AddressTags { get; set; }
}

The Model Builder mappings:

public DbSet<Address> Addresses { get; set; }
    public DbSet<AddressTag> AddressTag { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AddressTag>()
            .ToTable("AddressTag");

        modelBuilder.Entity<AddressTag>()
            .HasKey(t => new { t.EntityId, t.TagId });
        modelBuilder.Entity<AddressTag>()
        .HasOne(pt => pt.Entity)
            .WithMany(p => p.EntityTags)
            .HasForeignKey(p => p.EntityId);
        modelBuilder.Entity<AddressTag>()
        .HasOne(pt => pt.Tag)
            .WithMany(p => p.AddressTags)
            .HasForeignKey(p => p.TagId);

    }

There is an error when EF try to fetch Tags

An unhandled exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.dll Additional information: Invalid column name 'AddressId'.

I dont even have that Id convention.

Note: when I place explicitly public ICollection<AddressTag> EntityTags { get; set; } public ICollection<AddressTag> EntityTags { get; set; } public ICollection<AddressTag> EntityTags { get; set; } inside Address POCO, then it works perfectly, including EntityTags.Tag too. Thanks for any help :)

The issue has nothing to do with generic and/or abstract base entity classes.

First, to make your sample model compile, I've added the following classes

public abstract class EntityBase
{
    public long Id { get; set; }
}

public abstract class EntityTagBase
{
    public long TagId { get; set; }
    public Tag Tag { get; set; }
}

modified the AddressTag class as follows:

public class AddressTag : EntityTagBase
{
    public long EntityId { get; set; }
    public Address Entity { get; set; }
}

and added where T : EntityTagBase constraint to TaggedEntityBase<T> class to allow Tag property accessor inside Select(x => x.Tag) .

So far so good. The Tag related part of generated migration looks like this:

migrationBuilder.CreateTable(
    name: "Tags",
    columns: table => new
    {
        Id = table.Column<long>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
        AddressId = table.Column<long>(nullable: true),
        Name = table.Column<string>(nullable: true)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Tags", x => x.Id);
        table.ForeignKey(
            name: "FK_Tags_Addresses_AddressId",
            column: x => x.AddressId,
            principalTable: "Addresses",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
    });

See the AddressId column and FK to Addresses table? Why is that? Because of your Tags property:

public List<Tag> Tags { get { return ...; } }

It's probably a current EF Core bug of mapping a read only collection property, but the net effect is that it considers one to many relationship between Address and Tag which of course is not your intention.

In general I would recommend keeping the entity model clean and not include such "helper" properties - both collection and reference type. They look like navigation properties, but they are not, and it's easy to use them by mistake inside a query, which will totally change the execution plan and lead to unexpected exceptions or wrong results (in case the underlying property is not loaded). Not speaking about the violation of a general rule to not create property returning List which is not a member of the class, but created in every property access call.

Shortly, simply remove that property and the problem will be gone. Or if you insist keeping it, then decorate it with NotMapped data annotation:

[NotMapped]
public List<Tag> Tags { get { return ...; } }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM