Use Discriminator to handle other tables like TPT with fluent for EF Core

0

I have this table that I want to track editing of other tables with.

namespace fais.accounting.Models.COA 
{
    public class coaEdits
    {
        public string entity_id { get; set; }
        public string editor_id { get; set; }

        [Display(Name = "Edited on")]
        public DateTime occured_on { get; set; } = DateTime.Now;

        public User editor { get; set; }
        public string entityType { get; set; }
        public string entity { get; set; } // If I coaEdits<Tkey> I can't set public Tkey entity, but it should be the object of the entityType's class right?
    }
}

That table I hoped would work where the foreign keys would be the entity_id.

All the other tables/classes are inheriting this class which has the navigation to the coaEdits

public class coa_base
{
    [Column(Order = 2)]
    public string title { get; set; }

    [Display(Name = "Entry date")]
    public DateTime entry_date { get; set; }

    [Required]
    [Display(Name = "Last updated")]
    public DateTime updated_on { get; set; } = DateTime.Now;

    public string creator_id { get; set; } //= @TODO userManage.get_current_user <- how to default?
    public User creator { get; set; }


    public virtual List<coaEdits> edits { get; set; }

}

Where an example of on of the tables is

public class budgets : coa_base
{
    [Column(Order = 1)]
    [Display(Name = "Budget")]
    public string budget_id { get; set; }

    public virtual List<coa> accounts { get; set; }
}

When I run

dotnet ef migrations add setsUpBaseClassModelOnCOA --context ApplicationDbContext

I get:

       migrationBuilder.CreateTable(
            name: "coaEdits",
            columns: table => new
            {
                entity_id = table.Column<string>(nullable: false),
                editor_id = table.Column<string>(nullable: false),
                occured_on = table.Column<DateTime>(nullable: false),
                editorId = table.Column<string>(nullable: true),
                entity_type = table.Column<string>(maxLength: 200, nullable: false),
                // everything below this seems like it should be wrapped up with 
                // key as entity_id and typed by entity_type, but it is not?
                entity = table.Column<string>(nullable: true), // that should be an object?  What the?
                budgetsbudget_id = table.Column<string>(nullable: true), // should be it should be the entity_id 
                coaaccount_id = table.Column<string>(nullable: true), // didto
                fundsfund_id = table.Column<string>(nullable: true), // didto
                // and so on
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_coaEdits", x => new { x.entity_id, x.editor_id, x.occured_on });
                table.ForeignKey(
                    name: "FK_coaEdits_coa_budgets_budgetsbudget_id",
                    column: x => x.budgetsbudget_id,
                    principalTable: "coa_budgets",
                    principalColumn: "budget_id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_coa_accounts_coaaccount_id",
                    column: x => x.coaaccount_id,
                    principalTable: "coa_accounts",
                    principalColumn: "account_id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_AspNetUsers_editorId",
                    column: x => x.editorId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_coa_funds_fundsfund_id",
                    column: x => x.fundsfund_id,
                    principalTable: "coa_funds",
                    principalColumn: "fund_id",
                    onDelete: ReferentialAction.Restrict);
                // and so on
            });

while I was expecting to get:

       migrationBuilder.CreateTable(
            name: "coaEdits",
            columns: table => new
            {
                entity_id = table.Column<string>(nullable: false),
                editor_id = table.Column<string>(nullable: false),
                occured_on = table.Column<DateTime>(nullable: false),
                editorId = table.Column<string>(nullable: true),
                entity_type = table.Column<string>(maxLength: 200, nullable: false),

            },
            constraints: table =>
            {
                table.PrimaryKey("PK_coaEdits", x => new { x.entity_id, x.editor_id, x.occured_on });
                table.ForeignKey(
                    name: "FK_coaEdits_coa_budgets_budgetsbudget_id",
                    column: x => x.entity_id,
                    principalTable: "coa_budgets",
                    principalColumn: "budget_id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_coa_accounts_coaaccount_id",
                    column: x => x.entity_id,
                    principalTable: "coa_accounts",
                    principalColumn: "account_id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_AspNetUsers_editorId",
                    column: x => x.editorId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_coaEdits_coa_funds_fundsfund_id",
                    column: x => x.entity_id,
                    principalTable: "coa_funds",
                    principalColumn: "fund_id",
                    onDelete: ReferentialAction.Restrict);
                // and so on
            });

The fluent options on the builder are

builder.Entity<coaEdits>().HasKey(sc => new { sc.entity_id, sc.editor_id, sc.occured_on });
builder.Entity<coaEdits>(b =>
                           {
                               b.HasDiscriminator<string>("entityType");
                               b.Property(e => e.entityType)
                                       .HasMaxLength(200)
                                       .HasColumnName("entity_type");
                           });

builder.Entity<coa>().ToTable("coa_accounts");
builder.Entity<funds>().ToTable("coa_funds");
// and so on

What is the best way to correct this? Is there a better approach? The missing part to this is

public string entity { get; set; }

I can't seem to figure out how to turn that in to the class from the discriminator which I would expect to be able to do. Also wouldn't the discriminator need to be a key too so it would be a key of entity_type and entity_id

EDIT/UPDATE: I was trying to get that type to get applied like this

public class coaEdits<TKey>
{
    public string entity_id { get; set; }
    public string editor_id { get; set; }

    [Display(Name = "Edited on")]
    public DateTime occured_on { get; set; } = DateTime.Now;

    public User editor { get; set; }
    public string entityType { get; set; }
    [NotMapped]
    public TKey entity { get; set; }
}

And the base class like this:

public class coa_base<TKey>
{
    [Column(Order = 2)]
    public string title { get; set; }

    [Display(Name = "Entry date")]
    public DateTime entry_date { get; set; }

    [Required]
    [Display(Name = "Last updated")]
    public DateTime updated_on { get; set; } = DateTime.Now;

    public string creator_id { get; set; } //= userManage.get_current_user
    public User creator { get; set; }

    public virtual List<coaEdits<TKey>> edits { get; set; }
}

Where an example class would be like

public class budgets : coa_base<budgets>
{
    [Column(Order = 1)]
    [Display(Name = "Budget")]
    public string budget_id { get; set; }

    public virtual List<coa> accounts { get; set; }

}

Using this instead for the fluent on builder options

builder.Entity<coaEdits<IKey>>().HasKey(sc => new { sc.entity_id, sc.editor_id, sc.occured_on });
builder.Entity<coaEdits<IKey>>(b =>
                                  {
                                      b.HasDiscriminator<string>("entityType");
                                      b.Property(e => e.entityType).HasColumnName("entity_type");
                                  });

But I get this error instead now

The entity type 'coaEdits' requires a primary key to be defined.

but that is set I thought by

builder.Entity<coaEdits<IKey>>().HasKey(sc => new { sc.entity_id, sc.editor_id, sc.occured_on });

If I test it with it all hard coded in, where I do

builder.Entity<coaEdits<budgets>>() . . 

I get a table of coaEdits<budgets> wanting to be created.

If I try to counter that table name with builder.Entity<coaEdits<budgets>>().ToTable("coa_edits"); it then says that another one, coaEdits<funds> and coaEdits<budgets> can't share the table because they don't share a primary key, but they do I thought entity_id

Additional UPDATE: Continuing on the coaEdits<TKey> approach so that public string entity { get; set; } gets a type, I figured that if the fluent api was not working to set the primary key, that I would try to use the attributes to get around this, but that has produced this error

Entity type 'coaEdits<budgets>' has composite primary key defined with data annotations. To set composite primary key, use fluent API.

From just doing this.

public class coaEdits<TKey>
{
    [Key]
    public string entity_id { get; set; }
    [Key]
    public string editor_id { get; set; }
    // and so on

ADDITIONAL INFORMATION: if I alter the migration the way I am thinking it should be set up to get the table correct, I get another error that doesn't make sense in the context. Here i will set the coaEdits table first

        migrationBuilder.CreateTable(name: "coaEdits", columns: table => new {
            entity_id = table.Column<string>(nullable: true)
            , editor_id = table.Column<string>(nullable: true)
            , occured_on = table.Column<DateTime>(nullable: false)
            , entity_type = table.Column<string>(nullable: false)
        }, constraints: table => {
                            table.PrimaryKey("PK_coaEdits", x => x.occured_on);
                            table.ForeignKey(name: "FK_coaEdits_AspNetUsers_editorId", column: x => x.editor_id
                                             , principalTable: "AspNetUsers", principalColumn: "Id"
                                             , onDelete: ReferentialAction.Restrict);
                            table.ForeignKey(name: "FK_coaEdits_coa_budgets_entity_id", column: x => x.entity_id
                                             , principalTable: "coa_budgets", principalColumn: "budget_id"
                                             , onDelete: ReferentialAction.SetNull);
                            table.ForeignKey(name: "FK_coaEdits_coa_funds_entity_id", column: x => x.entity_id
                                             , principalTable: "coa_funds", principalColumn: "fund_id"
                                             , onDelete: ReferentialAction.SetNull);
                            // and so on
                        });

And the example entity budgets is set up like this

        migrationBuilder.CreateTable(
            name: "coa_budgets",
            columns: table => new
            {
                entry_date = table.Column<DateTime>(nullable: false),
                updated_on = table.Column<DateTime>(nullable: false),
                title = table.Column<string>(nullable: true),
                creator_id = table.Column<string>(nullable: true),
                budget_id = table.Column<string>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_coa_budgets", x => x.budget_id);
                table.ForeignKey(
                    name: "FK_coa_budgets_AspNetUsers_creator_id",
                    column: x => x.creator_id,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            });

but when I run dotnet ef database update --context ApplicationDbContext leading to this error

Column 'coa_budgets.budget_id' is not the same length or scale as referencing column 'coaEdits.entity_id' in foreign key 'FK_coaEdits_coa_budgets_entity_id'. Columns participating in a foreign key relationship must be defined with the same length and scale.

The full error is:

Failed executing DbCommand (21ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [coaEdits] (
    [entity_id] nvarchar(max) NULL,
    [editor_id] nvarchar(max) NULL,
    [occured_on] datetime2 NOT NULL,
    [entity_type] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_coaEdits] PRIMARY KEY ([occured_on]),
    CONSTRAINT [FK_coaEdits_AspNetUsers_editor_id] FOREIGN KEY ([editor_id]) REFERENCES [AspNetUsers] ([Id]) ON DELETE NO ACTION,
    CONSTRAINT [FK_coaEdits_coa_budgets_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_budgets] ([budget_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_funds_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_funds] ([fund_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_objects_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_objects] ([object_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_programs_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_programs] ([program_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_projects_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_projects] ([project_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_accounts_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_accounts] ([account_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_subobjects_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_subobjects] ([subobject_id]) ON DELETE SET NULL,
    CONSTRAINT [FK_coaEdits_coa_subfunds_entity_id] FOREIGN KEY ([entity_id]) REFERENCES [coa_subfunds] ([subfund_id]) ON DELETE SET NULL
);
System.Data.SqlClient.SqlException (0x80131904): Column 'coa_budgets.budget_id' is not the same length or scale as referencing column 'coaEdits.entity_id' in foreign key 'FK_coaEdits_coa_budgets_entity_id'. Columns participating in a foreign key relationship must be defined with the same length and scale.

As I read everything, I have set up all the foreign keys matching.. the entity_id is [entity_id] nvarchar(max) NULL, where the budget is [budget _id] nvarchar(max) NULL, so I'm not sure why SQL threw that error.

c#
entity-framework
database-design
.net-core
discriminator
asked on Stack Overflow Jul 1, 2018 by Quantum • edited Jul 1, 2018 by Quantum

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0