将 MySql 数据库中的 JSON 列映射到 Web Api 中的 C# 类

Posted

技术标签:

【中文标题】将 MySql 数据库中的 JSON 列映射到 Web Api 中的 C# 类【英文标题】:Map JSON column from MySql Database to C# Class from Web Api 【发布时间】:2020-06-05 15:35:17 【问题描述】:

我有一个 mysql 数据库,其中包含 Id intName:json

地点表示例

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

C# Place 类

public class Place

        [Key, Column("id")]
        public int Id  get; set; 

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

我正在与 EntityFramework 6 连接,连接成功并检索这样的数据

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


我想要如何将名称映射到 new Object 而不是 JSON string

类似的东西

放置类

public class Place

        [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

当我这样做时,Name 属性带有 NULL


存储库中的代码

using (var context = new PlacesEntityModel())

     return context.Places.Take(5).ToList();

我不想使用AutoMapper

我希望 EntityFramework 中的某些内容在 数据库级别 中仅选择 一种语言,而不获取所有其他数据然后对其进行映射

如何解决这个问题?

【问题讨论】:

不确定这会有多大帮助,但请查看此处的声明 docs.microsoft.com/en-us/archive/msdn-magazine/2017/april/… 赏金只针对一种语言 :) 您可以使用 MySql 内联查询 " SELECT name-&gt;'$.ar' FROM places " 执行此操作,这将访问 JSON 路径并仅从 JSON 对象返回属性 ar 那么,看看这篇文章devart.com/dotconnect/mysql/docs/EF-JSON-Support.html 这可能是因为该库能够构建该功能。您需要弄清楚他们做了什么(逆向工程) 【参考方案1】:

您可以尝试扩展方法来映射您的实体类型。

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

扩展方法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;
    

用法

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");

【讨论】:

【参考方案2】:

首先,通过使用具有每种语言属性的类,您可以限制自己。如果您添加新语言,您总是必须添加新属性,这当然是可行的,但不必要的复杂。此外,您通常会将语言作为字符串对象(或能够转换),因此这将导致这样的代码

Localized<string> name = ...;
switch(language)

    case "en":
        return name.en;
    case "ar":
        return name.ar;
    case "fr":
        return name.fr;
    default:
        throw new LocalizationException();

这容易出错且过于复杂。对于您的问题,我想我会选择使用某种字典

IDictionary<string, string> names = ...;
if(names.ContainsKey(language))

    return names[language];

else 

    throw new LocalizationException();

只需在字典中添加更多翻译即可轻松扩展。

要将您的 JSON string 转换为 IDcitionary&lt;string, string&gt;,您可以使用以下代码

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

在你的班级里,这实际上是

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());
        
    

本地化的值可以像这样访问

var localizedPlaceName = place.LocalizedNames[language];

请注意:根据您的需求和用例,您应该考虑以下问题:

缓存

在我的 sn-p 中,每次访问本地化名称时都会解析 JSON string。根据您访问它的频率,这可能会损害性能,这可以通过缓存结果来缓解(不要忘记在设置Name 时删除缓存)。

关注点分离

应该是一个纯模型类。您可能希望引入封装所呈现逻辑的域类,而不是将逻辑添加到模型类中。拥有一个基于可本地化对象和语言创建易于本地化对象的工厂也是一种选择。

错误处理

在我的代码中没有错误处理。根据输入的可靠性,您应该考虑额外的错误处理。

【讨论】:

【参考方案3】:

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

就像@Nkosi 所说的那样

那么,看看这篇文章devart.com/dotconnect/mysql/docs/EF-JSON-Support.html

这可能是因为该库能够在其中构建该功能。您需要弄清楚他们做了什么(逆向工程)

【讨论】:

【参考方案4】:

我通常只使用 JSON.Net,我注意到另一个答案引用了 JObject,但没有深入探讨您的数据模型是否是正确的模型,我通常发现您可以这样做:

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

我注意到你的类上有 ComponentModel 属性。我不知道这些 JSon.Net 支持多少,你必须研究一下。它肯定支持 XML 序列化的一些属性,也有一些自己的。

请注意,您也可以将 JSOn 数组转换为列表:

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

【讨论】:

【参考方案5】:

我希望 EntityFramework 中的某些内容仅选择 一种语言 数据库级别,无需获取所有其他数据然后对其进行映射

如果您希望它来自数据库级别,您始终可以创建一个视图,然后将该视图包含在您的项目中。 示例:

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

这将创建一个模型类,例如:

public class PlacesLocalized

    public int Id  get; set; 

    public string en get; set;

    public string ar get; set;

    public string fr get; set;

然后,你可以这样做:

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

但如果您没有足够的权限在数据库级别执行此操作,那么您需要在 EF 中指定查询。没有简单的方法可以仅针对特定类更改 Entity Framework 的执行逻辑。这就是实体框架包含SqlQuery 方法的原因,它可以在需要时提供更大的灵活性来进行自定义查询(如您的)。

因此,如果您需要从 Entity Framework 指定本地化,那么您将创建一个存储库类来指定您需要的所有自定义查询,包括创建所需的任何 DTO

基本的方法是这样的:

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);
    

现在,你可以这样做了:

using(var repo = new PlaceRepo())

    var places = repo.GetPlacesLocalized(Localized.Arabic);

【讨论】:

【参考方案6】:
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;

我们可以使用显式运算符而不使用自动映射器来实现这一点

【讨论】:

以上是关于将 MySql 数据库中的 JSON 列映射到 Web Api 中的 C# 类的主要内容,如果未能解决你的问题,请参考以下文章

将 MySQL json 列映射到 JPA 会引发错误

如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性

将 PostgreSQL JSON 列映射到 Hibernate 实体属性

如何将 json 字符串数据类型列转换为配置单元中的映射数据类型列?

如何使用 JPA/Hibernate 注释将 MySQL char(n) 列映射到实例变量?

如何将 SQL 数据映射到 Java JSON 对象和 JSON 数组?