In my EF Fluent API configurations, I have a number of classes that derive from an abstract base class
public abstract class ImageBase
{
public int ImageId { get; set; }
public string ImageTitle { get; set; }
public string ImageFileExtension { get; set; }
public string ImageDescription { get; set; }
[NotMapped]
public string ImageFileName => BuildImageFilename();
public string ImageUrl { get; set; }
//set this property to true when part of a collection
//if it is the main image of the collection
public bool IsMainImage { get; set; }
//for consistency, all image filenames are the image title, with spaces replaced with underscores
//and the image file extension
private string BuildImageFilename()
{
return $"{ImageTitle.Replace(" ", "_")}{ImageFileExtension}";
}
}
I have a number of classes that derive from this, one example below
public class ArticleImageUri : ImageBase
{
[Required]
public int ArticleId { get; set; }
}
In my fluent API for that class I originally had the following
public void Configure(EntityTypeBuilder<ArticleImageUri> builder)
{
//configure primary key
builder.HasKey(u => new { u.ImageId, u.ArticleId })
.HasName("PK_ArticleImageUri");
//configure properties
builder.Property(u => u.ArticleId)
.ValueGeneratedNever()
.IsRequired();
builder.Property(u => u.ImageId)
.ValueGeneratedOnAdd()
.IsRequired();
builder.Property(u => u.ImageDescription)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Descriptions)
.IsRequired();
builder.Property(u => u.ImageFileExtension)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.ShortText)
.IsRequired();
builder.Property(u => u.ImageTitle)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageFileName)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageUrl)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Urls)
.IsRequired();
//ConfigurationHelpers.SetColumnSizesForImageClasses(builder);
}
Note: ConfigurationHelpers.MaxStringLengths is a static class that returns an int for the size of the column. I refactored the API code to a method, SetColumnSizesForImageClasses.
public static void SetColumnSizesForImageClasses(EntityTypeBuilder<ArticleImageUri> builder)
{
//configure column sizes
builder.Property(u => u.ImageDescription)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Descriptions)
.IsRequired();
builder.Property(u => u.ImageFileExtension)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.ShortText)
.IsRequired();
builder.Property(u => u.ImageTitle)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageFileName)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageUrl)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Urls)
.IsRequired();
}
and that works fine; for this class only.
I thought it would work just passing in the builder, as the builder is already of type but that doesn't work. I have a few other derived image types, for example.
public class MachineImageUri : ImageBase
{
[Required]
public int MachineId { get; set; }
public byte[] ImageThumbnail { get; set; }
}
I want to be able to use the refactored method to define the column sizes for the duplicate fields in every derived types Fluent API configuration but I cant figure out how to pass the proper parameters. I tried using T, which I think is the way to go, but could get no variant of that to work.
Since EntityTypeBuilder<T>
is invariant, you won't be able to reuse Configure
method out of the box. In other words you'll still need two (or more) distinct EF model configurations.
What you can do is to create "helper" method, which will take any T
with certain type constraint.
static void ConfigureBase<T>(EntityTypeBuilder<T> builder) where T : ImageBase
{
builder.Property(u => u.ImageDescription)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Descriptions)
.IsRequired();
builder.Property(u => u.ImageFileExtension)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.ShortText)
.IsRequired();
builder.Property(u => u.ImageTitle)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageFileName)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.FileNames)
.IsRequired();
builder.Property(u => u.ImageUrl)
.HasMaxLength(ConfigurationHelpers.MaxStringLengths.Urls)
.IsRequired();
}
And then you'll be able to reuse it in a following way:
ArticleImageUri
configuration:
public void Configure(EntityTypeBuilder<ArticleImageUri> builder)
{
ConfigureBase<ArticleImageUri>(builder);
//other specific configurations
}
MachineImageUri
configuration:
public void Configure(EntityTypeBuilder<MachineImageUri> builder)
{
ConfigureBase<MachineImageUri>(builder);
//other specific configurations
}