层次化数据和使用LINQ合并两个数据集。

huangapple go评论65阅读模式
英文:

Hierarchical data and merging two data sets with LINQ

问题

我正在尝试合并来自两个不同服务调用的数据。数据的结构类似于以下内容:

在 API 层的 DTOs:

public static class GetData
{
    public record Country
    {
        public Guid Id { get; set; }
        public List<State> States { get; set; }
    }

    public record State
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List<City> Cities { get; set; }
    }

    public record City
    {
        // 这个 Id 存储在服务 B 的数据库中作为 ItemId
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Location Location { get; set; }
    }

    // 来自服务 B
    public record Location
    {
        public Guid Id { get; set; }
        public Guid ItemId { get; set; }
        public string Name { get; set; }
        public string Latitude { get, set; }
        public string Longitude { get; set; }
    }
}

我需要将位置信息合并到正确的城市中,并在一次调用中返回上面的整个结构,例如:

// 获取包括城市(但不包括位置)的数据:
var country = await _serviceRequestA.GetCountryData();

// 获取用于第二次调用的 Id 列表:
var ids = country.States.SelectMany(x => x.Cities).Select(x => x.Id).ToList();

// 基于这些 Id 获取位置对象的列表:
var locations = await _serviceRequestB.GetLocations(ids); 

我尝试:通过 city.Id 和 location.ItemId 将每个城市与每个位置进行匹配,并在找到时将位置的 Id、Name、Latitude 和 Longitude 附加到城市作为 Location 对象。示例如下:

// 在每个城市上执行类似以下的操作:
// ...但保留之前的所有数据
cities.ForEach(c =>
 {
  c.Location = locations.Where(x => x.ItemId == c.Id)
  .FirstOrDefault();
 });

但如何将每个父对象和其子对象(包括城市)保留到 city 层级?我的数据是 country.states[n].cities[n]。除了简单的 where 和 select 之外,我在 C# 和 LINQ 中没有太多经验。我是否需要执行多个循环,并获取每个级别的属性,然后应用合并逻辑?是否有更简洁的 LINQ 方法?

PS- 这是当前从第一次服务调用返回的数据的 JSON 表示,以及合并后我希望的结果的另一个 JSON 表示。我需要将 Location 对象附加到每个匹配的城市:

{
  "country": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "US",
    "states": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "California",
        "cities": [
          {
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "name": "LA"
          }
        ]
      },
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Maine",
        "cities": [
          {
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "name": "Bangor"
          }
        ]
      }
    ]
  }
}

因此,当我将数据返回给前端时,城市将如下所示:

{
  "country": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "US",
    "states": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "California",
        "cities": [
          {
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "name": "LA",
            "location": {
               "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
               "name": "Location X",
               "latitude": "xxx",
               "longitude": "xx-xx"
              }
          }
        ]
      },
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Maine",
        "cities": [
          {
            "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "name": "Bangor",
            "location": {
               "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
               "name": "Location Y",
               "latitude": "yy",
               "longitude": "yy-yy"
              }
          }
        ]
      }
    ]
  }
}
英文:

I am trying to merge data from two different services calls. Data is similar in structure to this:

DTOs In Api Layer:

public static class GetData
{
    public record Country
    {
        public Guid Id { get; set; }
        public List&lt;State&gt; States { get; set; }
    }

    public record State
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List&lt;City&gt; Cities { get; set; }
    }

    public record City
    {
        // this Id is stored in Service B&#39;s DB as ItemId
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Location Location { get; set; }
    }

    // From service B
    public record Location
    {
        public Guid Id { get; set; }
        public Guid ItemId { get; set; }
        public string Name { get; set; }
        public string Latitude { get; set; }
        public string Longitude { get; set; }
    }
}

I need to merge the Locations into the correct city and return the entire structure above in one shot. eg.

// this gets data including city (but no location) :
var country = await _serviceRequestA.GetCountryData();

// get list of Ids to make second call:
var ids = country.States.SelectMany(x =&gt; x.Cities).Select(x =&gt; x.Id).ToList();

// get list of location objects based on these ids:
var locations = await _serviceRequestB.GetLocations(ids); 

Trying to: match each City with each Location via city.Id and location.ItemId, and when found, append the location's Id, Name, Latitude, Longitude as a Location object to the City.

// do something like this on each city:
// ...but all previous data,
cities.ForEach(c =&gt;
 {
  c.Location = locations.Where(x =&gt; x.ItemId == c.Id)
  .FirstOrDefault();
 });

But how do I take each parent object and children as-is up to and including city?

Again, my data is: country.states[n].cities[n].

I'm not that experienced with C# or LINQ other than simple wheres and selects. Do I have to do multiple loops and grab each level's props and then apply the merge logic? Is there a more succinct way in LINQ?

PS- here is a json representation of what is currently returned from the first service call, and another with the result I want after merge. I need to append the Location object to each matching city:

{
  &quot;country&quot;: {
    &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
    &quot;name&quot;: &quot;US&quot;,
    &quot;states&quot;: [
      {
        &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
        &quot;name&quot;: &quot;California&quot;,
        &quot;cities&quot;: [
          {
            &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
            &quot;name&quot;: &quot;LA&quot;
          }
        ]
      },
      {
        &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
        &quot;name&quot;: &quot;Maine&quot;,
        &quot;cities&quot;: [
          {
            &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
            &quot;name&quot;: &quot;Bangor&quot;
          }
        ]
      }
    ]
  }
}

So the cities will then look like this when I return data to frontend:

{
  &quot;country&quot;: {
    &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
    &quot;name&quot;: &quot;US&quot;,
    &quot;states&quot;: [
      {
        &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
        &quot;name&quot;: &quot;California&quot;,
        &quot;cities&quot;: [
          {
            &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
            &quot;name&quot;: &quot;LA&quot;,
            &quot;location&quot;: {
               &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
               &quot;name&quot;: &quot;Location X&quot;,
               &quot;latitude&quot;: &quot;xxx&quot;,
               &quot;longitude&quot;: &quot;xx-xx&quot;
              }
          }
        ]
      },
      {
        &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
        &quot;name&quot;: &quot;Maine&quot;,
        &quot;cities&quot;: [
          {
            &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
            &quot;name&quot;: &quot;Bangor&quot;,
            &quot;location&quot;: {
               &quot;id&quot;: &quot;3fa85f64-5717-4562-b3fc-2c963f66afa6&quot;,
               &quot;name&quot;: &quot;Location Y&quot;,
               &quot;latitude&quot;: &quot;yy&quot;,
               &quot;longitude&quot;: &quot;yy-yy&quot;
              }
          }
        ]
      }
    ]
  }
}

答案1

得分: 1

以下是您要翻译的代码部分:

From your question and comment, I think this is what you want. 

cities.ForEach(c =>
{
    c.Location = locations.Where(x => x.ItemId == c.Id).FirstOrDefault();
});

Or if you do not want to include `Itemid`'s value, you can also use this code:

cities.ForEach(c =>
{
    c.Location = locations.Where(x => x.ItemId == c.Id).Select(x => new LocationA(){Id= x.Id,Name= x.Name,Latitude=x.Latitude,Longitude=x.Longitude}).FirstOrDefault();
});

**Update**:

If you wanna access cities from country, just need to use `Foreach(xxx)` twice. Please note that if you want to output json without itemid property instead of the value of itemid is null, you need to change your code like:

public class City
{
    // this Id is stored in Service B's DB as ItemId
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Object Location { get; set; }
}

Then:

country.States.ForEach(x =>
{
    x.Cities.ForEach(y =>
    {
        y.Location = locationAs.Where(z => z.ItemId == y.Id).Select(d => new {Id=d.Id,Name = d.Name, Latitude = d.Latitude, Longitude = d.Longitude }).FirstOrDefault();
    });
});

Output:

请注意,代码的格式和标记可能会受到影响,因为纯文本中无法保留原始格式和标记。

英文:

From your question and comment, I think this is what you want.

cities.ForEach(c =&gt;
            {
                c.Location = locations.Where(x =&gt; x.ItemId == c.Id).FirstOrDefault();
            });

Or if you do not want to include Itemid's value , you can also use this code:

cities.ForEach(c =&gt;
            {
                c.Location = locations.Where(x =&gt; x.ItemId == c.Id).Select(x =&gt; new LocationA(){Id= x.Id,Name= x.Name,Latitude=x.Latitude,Longitude=x.Longitude}).FirstOrDefault();
            });

Update:

If you wanna access cities from country, just need to use Foreach(xxx) twice. Please note that if you want to output json without itemid property instead of the value of itemid is null, you need to change your code like:

public class City
    {
        // this Id is stored in Service B&#39;s DB as ItemId
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Object Location { get; set; }
    }

Then:

country.States.ForEach(x =&gt;
            {
                x.Cities.ForEach(y =&gt;
                {
                    y.Location = locationAs.Where(z =&gt; z.ItemId == y.Id).Select(d=&gt; new {Id=d.Id,Name = d.Name, Latitude = d.Latitude, Longitude = d.Longitude }).FirstOrDefault();
                });
            });

Output:

层次化数据和使用LINQ合并两个数据集。

You can see even the property not map a data, It will just show null.

huangapple
  • 本文由 发表于 2023年3月1日 13:34:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75599919.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定