Ruben Szekér
Ruben Szekér

Reputation: 1175

How can I map lists of simple datatypes without having to stringify them?

I'm making a API in C# .NET Core 2.2 and I'm trying to figure out how to properly map Lists of Enums and Strings.

Right now I have the following solution:

        [NotMapped]
        public virtual IList<string> OtherNames { get; set; } = new List<string>();
        public string OtherName
        {
            get
            {
                return JsonConvert.SerializeObject(OtherNames);
            }
            set
            {
                OtherNames = JsonConvert.DeserializeObject<List<string>>(value);
            }
        }

and this works 100% like I want it to. BUT when I do an API call of a HttpGet for the object that has OtherNames as a property it returns OtherNames AND OtherName. Here a shortened visualization for context: "otherNames":[],"otherName":"[]".

I can see the solution to this going two ways:

  1. An easier mapping without needing a second property.

  2. A way to exclude either one of the properties when calling the HttpGet. ==> For this solution I'll add the responsible code for the HttpGet down here:

This are all the properties that get returned.

        public int Id { get; set; }
        public string Image { get; private set; }
        public string FirstName { get; private set; }
        public string LastName { get;  private set; }
        public Countries PlaceBorn { get; private set; }
        public DateTime WhenBorn { get; private set; }
        public DateTime? WhenDied { get; private set;}
        public string Description { get; set; }
        public Book Favorite { get; private set; }

        public IList<CelebrityRead> Read { get; set; } = new List<CelebrityRead>();
        public IList<Relationship> Relationships { get; set; } = new List<Relationship>();
        [NotMapped]
        public virtual IList<Countries> Nationalities { get; set; } = new List<Countries>();

        public string Nationality
        {
            get
            {
                return JsonConvert.SerializeObject(Nationalities);
            }
            set
            {
                Nationalities = JsonConvert.DeserializeObject<List<Countries>>(value);
            }
        }
        [NotMapped]
        public virtual IList<string> OtherNames { get; set; } = new List<string>();
        public string OtherName
        {
            get
            {
                return JsonConvert.SerializeObject(OtherNames);
            }
            set
            {
                OtherNames = JsonConvert.DeserializeObject<List<string>>(value);
            }
        }
        [NotMapped]
        public virtual IList<Jobs> Jobs { get; set; } = new List<Jobs>();
        public string Job
        {
            get
            {
                return JsonConvert.SerializeObject(Jobs);
            }
            set
            {
                Jobs = JsonConvert.DeserializeObject<List<Jobs>>(value);
            }
        }

Controller method, calls repository method

        [HttpGet("{id}")]
        public ActionResult<Celebrity> GetCelebrity(int id)
        {
            var celeb = _celebrityRepository.GetBy(id);
            if (celeb == null) return NotFound();
            return celeb;
        }

Repository method, called by controller method

        public Celebrity GetBy(int id)
        {
            return _celebrities
              .Include(c => c.Favorite)
              .Include(c => c.Read)
              .Include(c => c.Relationships)
                .ThenInclude(r => r.With)
              .SingleOrDefault(c => c.Id == id);
        }

Controller returns

{"id":-1,"image":".png","firstName":"Louis","lastName":"Sachar","placeBorn":249,"whenBorn":"1954-03-20T00:00:00","whenDied":null,"description":"...","favorite":null,"read":[],"relationships":[],"nationalities":[],"nationality":"[]","otherNames":[],"otherName":"[]","jobs":[],"job":"[]"}

Current mapping of Lists of simple datatypes is auto mapped by fluent API since it can automap the string property OtherName.

OtherNames is not mapped.

Upvotes: 1

Views: 64

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205829

Option 1 ("An easier mapping without needing a second property") could be achieved with EF Core Value Conversions:

Value converters allow property values to be converted when reading from or writing to the database. This conversion can be from one value to another of the same type (for example, encrypting strings) or from a value of one type to a value of another type (for example, converting enum values to and from strings in the database.)

Basically keep just this in the entity class:

public IList<string> OtherNames { get; set; } = new List<string>();

and associate value converter with it inside model configuration:

modelBuilder.Entity<Celebrity>()
    .Property(e => e.OtherNames)
    .HasConversion(
        value => JsonConvert.SerializeObject(value),
        dbValue => JsonConvert.DeserializeObject<List<string>>(dbValue));

The only potential problem could be the usage of OtherNames property inside LINQ to Entities query (for instance, to perform filtering) as mentioned in the current Value Conversion Limitations:

  • Use of value conversions may impact the ability of EF Core to translate expressions to SQL. A warning will be logged for such cases. Removal of these limitations is being considered for a future release.

Upvotes: 1

Related Questions