adding a resource approval feature and fighting against ef core

I got a request to add a feature to our cms (mainly for videos) for a workflow for approving asr tracks before adding them to the main video content. I see mainly two choices here: add necessary fields/columns to type Track or have a separate table for tracks to be approved. Since tracks are in their own type/table, and it results that this workflow is fundamentally an external process, my thought was to put tracks in a separate table and when/if they are approved they are moved to the original Tracks table and are linked to the video. see Track Here start all the problems, because ef core does not allow two tables on the same type. I thought oh well, I will use Track as owned type of a ResourceApproval container type, BUT Tracks has inheritance so again it doesn't work. see ResourceApproval So next step I tried was to separate Track in identity (id) + values (the other fields), so that I could use, in a sort of composition way, the values in ResourceApproval and avoid the inheritance... But again this causes other problems which I don't exactly understand, specifically creating a migration tells me that the relation Cues cannot be determined, even if I add fluent over attributes to make it more (?) explicit. see Track+TrackValues So I don't know, either I have to flatten all the fields to make this work or what am I doing wrong? 🤷‍♂️ I wouldn't want to use another DbContext because since I would have a new type in there it probably would be a mess with the migrations.
1 Reply
this_is_pain
this_is_painOP4d ago
Track
public class Track
{
public Track() => Cues = new List<Cue>();

public long ContentId { get; set; }

[MaxLength(511)]
public string Description { get; set; }

public DateTime CreationTime { get; set; }

public DateTime? LastModificationTime { get; set; }

[MaxLength(255)]
public string Name { get; set; }

public DateTimeOffset? Started { get; set; }

[Obsolete("Please shift Started field instead! Now is useless, not used anymore but kept only for porting purposes")]
public TimeSpan StartedOffset { get; set; }

[MaxLength(500)]
public string TimeZoneName { get; set; }

public TimeSpan TimeZoneOffset { get; set; }

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long TrackId { get; set; }

[InverseProperty(nameof(Cue.Track))]
public ICollection<Cue> Cues { get; set; }

[ForeignKey(nameof(ContentId))]
public Content Content { get; set; }
}
public class Track
{
public Track() => Cues = new List<Cue>();

public long ContentId { get; set; }

[MaxLength(511)]
public string Description { get; set; }

public DateTime CreationTime { get; set; }

public DateTime? LastModificationTime { get; set; }

[MaxLength(255)]
public string Name { get; set; }

public DateTimeOffset? Started { get; set; }

[Obsolete("Please shift Started field instead! Now is useless, not used anymore but kept only for porting purposes")]
public TimeSpan StartedOffset { get; set; }

[MaxLength(500)]
public string TimeZoneName { get; set; }

public TimeSpan TimeZoneOffset { get; set; }

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long TrackId { get; set; }

[InverseProperty(nameof(Cue.Track))]
public ICollection<Cue> Cues { get; set; }

[ForeignKey(nameof(ContentId))]
public Content Content { get; set; }
}
ResourceApproval
public sealed class ResourceApproval
{
public string? ApprovalComment { get; set; }

public ResourceApprovalStatus ApprovalStatus { get; set; }

public Guid? ApprovedBy { get; set; }

public DateTimeOffset? ApprovedOn { get; set; }

public ApprovalResourceType Discriminator { get; set; }

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ResourceApprovalId { get; set; }

public Tracks.Track Track { get; set; }
}
public sealed class ResourceApproval
{
public string? ApprovalComment { get; set; }

public ResourceApprovalStatus ApprovalStatus { get; set; }

public Guid? ApprovedBy { get; set; }

public DateTimeOffset? ApprovedOn { get; set; }

public ApprovalResourceType Discriminator { get; set; }

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ResourceApprovalId { get; set; }

public Tracks.Track Track { get; set; }
}
Track+TrackValues
public class Track : TrackValues
{
public Track() => Cues = new List<Cue>();

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long TrackId { get; set; }

[InverseProperty(nameof(Cue.Track))]
public ICollection<Cue> Cues { get; set; }

[ForeignKey(nameof(ContentId))]
public Content Content { get; set; }
}
public class TrackValues
{
public long ContentId { get; set; }

[MaxLength(511)]
public string Description { get; set; }

public DateTime CreationTime { get; set; }

public DateTime? LastModificationTime { get; set; }

[MaxLength(255)]
public string Name { get; set; }

public DateTimeOffset? Started { get; set; }

[Obsolete("Please shift Started field instead! Now is useless, not used anymore but kept only for porting purposes")]
public TimeSpan StartedOffset { get; set; }

[MaxLength(500)]
public string TimeZoneName { get; set; }

public TimeSpan TimeZoneOffset { get; set; }
}
public class Track : TrackValues
{
public Track() => Cues = new List<Cue>();

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long TrackId { get; set; }

[InverseProperty(nameof(Cue.Track))]
public ICollection<Cue> Cues { get; set; }

[ForeignKey(nameof(ContentId))]
public Content Content { get; set; }
}
public class TrackValues
{
public long ContentId { get; set; }

[MaxLength(511)]
public string Description { get; set; }

public DateTime CreationTime { get; set; }

public DateTime? LastModificationTime { get; set; }

[MaxLength(255)]
public string Name { get; set; }

public DateTimeOffset? Started { get; set; }

[Obsolete("Please shift Started field instead! Now is useless, not used anymore but kept only for porting purposes")]
public TimeSpan StartedOffset { get; set; }

[MaxLength(500)]
public string TimeZoneName { get; set; }

public TimeSpan TimeZoneOffset { get; set; }
}
also i could go even more composition and have values from Track-inherited classes not inherit TrackValue, but i would have to use interfaces because obv. you cannot inherit multiple concrete classes and so it would probably be as an effort as using flat fields no helpy helpy anyway atm i've deleted the types and flattened the fields, can't spend days on this

Did you find this page helpful?