4

I have a MySql database with columns Id int and Name:json

Places Table Sample

Id      Name
1       {"en":"Sphinx","ar":"أبو الهول","fr":"Le sphinx"}

C# Place class

public class Place
{
        [Key, Column("id")]
        public int Id { get; set; }

        [Column("name")]
        public string Name { get; set; }
}

I'm connecting with EntityFramework 6 and connection success and retrieve data like this

{Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" }


What I want how to Map Name to new Object not JSON string

something like this

Place class

public class Place
{
        [Key, Column("id")]
        public int Id { get; set; }

        [Column("name")]
        public Localized<string> Name { get; set; }
}

Localized class

public class Localized<T>
{
        public T en { get; set; } // english localization
        public T ar { get; set; } // arabic localization
        public T fr { get; set; } // french localization
}

when I do this Name property come with NULL value


Code in Repository

using (var context = new PlacesEntityModel())
{
     return context.Places.Take(5).ToList();
}

I don't want to use AutoMapper,

I want something in EntityFramework to select only one language in Database Level without fetching all other data and then map it

how to fix this?

6
  • Not sure how much help this would be but take a look at the statements here learn.microsoft.com/en-us/archive/msdn-magazine/2017/april/… Commented Feb 23, 2020 at 10:11
  • the bounty is to only one language :) Commented Feb 23, 2020 at 10:23
  • You can do this with MySql inline query " SELECT name->'$.ar' FROM places " this will access JSON paths and return only property ar from JSON object Commented Feb 23, 2020 at 10:29
  • 1
    In that case then, take a look at this article devart.com/dotconnect/mysql/docs/EF-JSON-Support.html Commented Feb 23, 2020 at 10:31
  • 1
    It probably can given that the library was able to build that feature in. You would need to figure out what they they did (reverse engineer) Commented Feb 23, 2020 at 10:37

6 Answers 6

3

You can try extension method to map from your entity type.

public class Place
{
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

public class PlaceDTO
{
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public Localized<string> Name { get; set; }
}

public class Localized<T>
{
    public T en { get; set; } // english localization
    public T ar { get; set; } // arabic localization
    public T fr { get; set; } // french localization
}

Extenstion Method ToDto

public static class Extensions
{
    public static PlaceDTO ToDto(this Place place)
    {
        if (place != null)
        {
            return new PlaceDTO
            {
                Id = place.Id,
                Name = JsonConvert.DeserializeObject<Localized<string>>(place.Name)
            };
        }

        return null;
    }
}

Usage

var place = new Place() { Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" };
var placeDTO = place.ToDto();

Console.WriteLine($"{placeDTO.Id}-{placeDTO.Name.ar}-{placeDTO.Name.en}-{placeDTO.Name.fr}");
Sign up to request clarification or add additional context in comments.

Comments

3

First of all, by using a class with a property per language, you restrict yourself. You'd always have to add new properties if you add new languages, which would of course be feasible, but unnecessary complicated. Furthermore you'd usually have the language as a string-ish object (or be able to convert), hence this would lead to code like this

Localized<string> name = ...;
switch(language)
{
    case "en":
        return name.en;
    case "ar":
        return name.ar;
    case "fr":
        return name.fr;
    default:
        throw new LocalizationException();
}

which is error-prone and overly complicated. For your problem, I think I'd opt to use some kind of dictionary

IDictionary<string, string> names = ...;
if(names.ContainsKey(language))
{
    return names[language];
}
else 
{
    throw new LocalizationException();
}

which is easily extensible by just adding more translations to the dictionary.

To convert your JSON string to an IDcitionary<string, string>, you could use the following code

localizedNames = JObject.Parse(Name)
                        .Children()
                        .OfType<JProperty>()
                        .ToDictionary(property => property.Name, 
                                      property => property.Value.ToString());

From within your class this would effectively be

public class Place
{
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }

    public Dictionary<string, string> LocalizedNames 
    {
        get
        {
            return JObject.Parse(Name)
                          .Children()
                          .OfType<JProperty>()
                          .ToDictionary(property => property.Name, 
                                        property => property.Value.ToString());
        }
    }
}

The localized values can be accessed like

var localizedPlaceName = place.LocalizedNames[language];

Please note: Depending on your needs and use cases, you should consider the following issues:

Caching

In my snippet, the JSON string is parsed every time the localized names are accessed. Depending on how often you access it, this might be detrimental to performance, which could be mitigated by caching the result (don't forget to delete the cache when Name is set).

Separation of concerns

The class as is is supposed to be a pure model class. You might want to introduce domain classes that encapsulate the presented logic, rather than adding the logic to the model class. Having a factory that creates readily localized objects based on the localizable object and the language could be an option, too.

Error handling

In my code there is no error handling. Depending on the reliability of input you should consider additional error handling.

Comments

1

devart.com/dotconnect/mysql/docs/EF-JSON-Support.html

Like what @Nkosi said

In that case then, take a look at this article devart.com/dotconnect/mysql/docs/EF-JSON-Support.html

It probably can given that the library was able to build that feature in. You would need to figure out what they they did (reverse engineer)

Comments

1

I usually just use JSON.Net, I notice that another answer referenced JObject, but without going into whether your data-model is the right model, I generally find that you can do:

var MyObjectInstance = JObject.Parse(myJsonString).ToObject<MyObjectType>();

I notice that you have ComponentModel attributes on your class. I don't know off hand how many of these JSon.Net supports, and you'd have to research that. It definitely supports some attributes from XML serialization, and also has some of it's own.

Note that you can also convert a JSOn array into a list:

var MyObjectList = JArray.Parse(myJsonString).ToObject<IEnumerable<MyObjectType>();

Comments

1
+50

I want something in EntityFramework to select only one language in Database Level without fetching all other data and then map it

if you want it to be from database level, you can always create a view and then include this view in your project. Example :

CREATE VIEW `PlacesLocalized` AS
SELECT 
    Id
,   TRIM(REPLACE(name->'$.en', '"','')) AS en   
,   TRIM(REPLACE(name->'$.ar', '"','')) AS ar
,   TRIM(REPLACE(name->'$.fr', '"','')) AS fr
FROM 
    places

This would create a model class Like :

public class PlacesLocalized
{
    public int Id { get; set; }

    public string en {get; set;}

    public string ar {get; set;}

    public string fr {get; set;}
}

Then, you can do :

var places = context.PlacesLocalized.Where(x=> x.en == "Sphinx");

But if you don't have enough permissions to do this in the database level, then you would need to specify the query in your EF. There is no easy way to change the execution logic of Entity Framework just for specific classes. That's why Entity Framework included SqlQuery method, which would give more flexibility to have custom queries when needed (like yours).

So, if you need to specify the localization from Entity Framework, then you would do a repository class to specify all custom queries you need including creating any DTO needed.

The basic way would be something like this :

public enum Localized
{
    English,
    Arabic,
    French
}

public class PlaceRepo : IDisposable
{
    private readonly PlacesEntityModel _context = new PlacesEntityModel();

    public List<Place> GetPlacesLocalized(Localized localized = Localized.English)
    {
        string local = localized == Localized.Arabic ? "$.ar"
                    : localized == Localized.French ? "$.fr"
                    : "$.en";

        return _context.Places.SqlQuery("SELECT Id, name-> @p0 as Name FROM places", new[] { local })
            .Select(x=> new Place { Id = x.Id, Name = x.Name.Replace("\"", string.Empty).Trim() })
            .ToList();
    }


    private bool _disposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
            _disposed = true;
        }
    }

    ~PlaceRepo()
    {
        Dispose(false);
    }
}

now, you can do this :

using(var repo = new PlaceRepo())
{
    var places = repo.GetPlacesLocalized(Localized.Arabic);
}

Comments

1
public class Place
  {
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }

    public static explicit operator Place(PlaceDTO dto)
    {
      return new Place()
      {
        Id = dto.Id,
        Name = dto.Name
      };
    }
  }

  public class PlaceDTO
  {
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public Localized<string> Name { get; set; }

    public static explicit operator PlaceDTO(Place pls)
    {
      return new PlaceDTO()
      {
        Id = pls.Id,
        Name = pls.Name
      };
    }
  }


  var placeDTO = (placeDto)place;

we can achieve this using explicit operator without using auto mapper

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.