【问题标题】:Map JSON column from MySql Database to C# Class from Web Api将 MySql 数据库中的 JSON 列映射到 Web Api 中的 C# 类
【发布时间】: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 中的某些内容在 数据库级别 中仅选择 一种语言,而不获取所有其他数据然后对其进行映射

如何解决这个问题?

【问题讨论】:

标签: c# mysql asp.net-mvc entity-framework asp.net-web-api


【解决方案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;
            

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

            【讨论】:

              猜你喜欢
              • 2015-02-14
              • 2011-09-28
              • 1970-01-01
              • 2018-09-02
              • 2014-06-24
              • 2019-02-22
              • 1970-01-01
              • 2022-01-16
              • 2013-06-12
              相关资源
              最近更新 更多