Reputation: 2665
I'm trying to convert a Enum[]
to string[]
and back to Enum[]
but when values are converted in the database they are converted as int[]
which in terms of reliability can change. If one item in the Enum type is removed later, the offset causes all values after that index to be invalid instead of just "one" item that perhaps get cast to a default value. Therefor, I'd like to convert any Enum[]
to string[]
similiar to string.Join.
However, the process seems overcomplicated for such a simple task. I'm wondering if there's anything I've missed?
Example in EF Core:
public enum EntityEnum
{
One,
Two,
Three
}
public class Entity
{
public EntityEnum[] Items { get; set; }
public static void GetEntityBuilder(EntityTypeBuilder<Entity> entity)
{
entity.Property(e => e.Items)
.HasMaxLength(100)
.HasDefaultValue<EntityEnum[]>(null)
.ValueGeneratedNever()
.IsRequired(false);
}
}
In the Database this will be "[0, 1, 2]" for:
new EntityEnum[] { EntityEnum.One, EntityEnum.Two, EntityEnum.Three }
but it should be "[One, Two, Three]". So if Two is removed at some point, Three is still Three and not 1.
I've tried manually with HasConversion
, like I said previously but it seems overcomplicated. In the end it requires a ValueComparer
(for a string). Also, changing the default conversion might break some built in selectors and whatnot? Therefor, I'd like to keep as much intact as possible.
Is there any solution for this? Or am I stuck with a manual conversion that potentially breaks some queries that could be supported using an int[]
saved in the database?
Updating with some clarifications:
Purpose of the question HasConversion is the preferred method I'm asking about. Without custom ValueComparer / ValueConverter - what types of queries are supported when saving EntityEnum[] as [0, 1, 2] in the database and why are they not supported when EntityEnum[] is saved as [One, Two, Three] or [Three, Two, One]? And is there a way to add support for similiar querying and why not? Standard JSON serializer handles any String that matches any Enum magically today. If both queries that "works" is done in code than I don't see the purpose of HasConversion if that breaks built in conversions. If the query is translated to an actual SQL query that is another thing entirely. But then again, it probably could handle that conversion just as well if there are int[], string[] since it's still an varchar column in the end (it's technically all just strings). It has to do some magic there as well, converting it to something queryable if it does the query in the actual database.
Upvotes: 0
Views: 112
Reputation: 1499
If one item in the Enum type is removed later, the offset causes all values after that index to be invalid instead of just "one" item that perhaps get cast to a default value.
If that is your only concern, the simplest way is to set the value for your enum instead of using the default values:
public enum EntityEnum
{
One = 1,
Two = 20,
Three = 999
}
Such that there will have no "offset" issue when you remove one of them later.
Upvotes: 0
Reputation: 1
If you're using the Data Annotation approach, the previous answer works. However, if you're using IEntityTypeConfiguration with Fluent API configurations, it does not.
I believe this approach provides better isolation since the developer doesn't even need to know that the database stores it as a string[] (in my case, PostgreSQL). From a clean code perspective, this keeps concerns separated and prevents unnecessary exposure of implementation details.
And yes, mapping a database column to a private variable in a class can be a bit mind-bending!
public class FooConfiguration : IEntityTypeConfiguration<Foo>
{
public void Configure(EntityTypeBuilder<Foo> builder)
{
builder.Property<string[]>("_itemsMapped")
.HasColumnName("Items")
.HasColumnType("text[]");
builder.Ignore(e => e.Items);
}
}
public class Foo
{
// This field is referenced indirectly as a string[] in the configuration.
// Do not remove it—it's a hack for EF Core .NET 8.0.
private string[] _itemsMapped;
public EntityEnum[] Items
{
get => _itemsMapped?.Select(str => Enum.Parse<EntityEnum>(str)).ToArray();
set => _itemsMapped = value?.Select(item => item.ToString()).ToArray();
}
}
Benefits
The developer never interacts with the string[] directly.
The property is always used as an EntityEnum[] in code.
Conversions between string[] and EntityEnum[] happen automatically.
This same approach could be adapted for databases that store arrays as comma-separated values—the theoretical principle remains the same.
Upvotes: 0
Reputation: 19384
I don't understand, why this has to be array of Enum. You can allways have un-mapped property on your model, and another, mapped property
[NotMapped]
public EntityEnum[] Items { get; set; }
[Column("Items")]
internal string[] ItemsMapped // NOTE - internal, not exposed to the app, only to EF
{
get
{
return Items.Select(item => item.ToString());
}
set
{
Items = value.Select(str => Enum.Parse<EntityEnum>(str)).ToArray();
}
}
Upvotes: 1