英文:
How to join two lists together using an id and map to an object that contains the properties from both lists in c#
问题
以下是您要求的翻译部分:
我有两个列表,它们的元素如下:
List<Model1> m1List = new List<Model1>();
List<Model2> m2List = new List<Model2>();
m1List[0].id = "1";
m1List[0].name = "a";
m1List[0].surName = "b";
m1List[1].id = "2";
m1List[1].name = "c";
m1List[1].surName = "d";
m1List[2].id = "5";
m1List[2].name = "e";
m1List[2].surName = "f";
*********
m2List[0].id = "1";
m2List[0].email = "aa@gmail.com";
m2List[0].phone = "555";
m2List[1].id = "2";
m2List[1].email = "bb@gmail.com";
m2List[1].phone = "44";
m2List[2].id = "3";
m2List[2].email = "cc@gmail.com";
m2List[2].phone = "77";
然后,我想创建一个新列表,其中包含两个列表中具有相同“id”值的记录。匹配的记录应该组合成一个新对象,该对象具有来自两个记录的属性。
List<Model1and2> m1Andm2List = new List<Model1and2>();
m1Andm2List[0].id = "1";
m1Andm2List[0].name = "a";
m1Andm2List[0].surName = "b";
m1Andm2List[0].email = "aa@gmail.com";
m1Andm2List[0].phone = "555";
m1Andm2List[1].id = "2";
m1Andm2List[1].name = "c";
m1Andm2List[1].surName = "d";
m1Andm2List[1].email = "bb@gmail.com";
m1Andm2List[1].phone = "44";
然后,我想将该列表附加到所有未匹配其他列表中的记录。由于没有匹配,未找到记录的属性应保留为空白,应类似于以下内容:
m1Andm2List[2].id = "3";
m1Andm2List[2].name = "";
m1Andm2List[2].surName = "";
m1Andm2List[2].email = "cc@gmail.com";
m1Andm2List[2].phone = "77";
m1Andm2List[3].id = "5";
m1Andm2List[3].name = "e";
m1Andm2List[3].surName = "f";
m1Andm2List[3].email = "";
m1Andm2List[3].phone = "";
因为“m1List”没有具有“id == 3”的记录,而“m2List”没有具有“id == 5”的记录。
如何在C#中将两个列表组合起来以生成期望的结果?
这是您所提供的代码和说明的翻译。如果您有任何其他问题或需要进一步的帮助,请随时告诉我。
英文:
I have two list and they have elements like this :
List<Model1> m1List= new List<Model1>();
List<Model2> m2List= new List<Model2>();
m1List[0].id= "1";
m1List[0].name = "a";
m1List[0].surName= "b";
m1List[1].id= "2";
m1List[1].name = "c";
m1List[1].surName= "d";
m1List[2].id= "5";
m1List[2].name = "e";
m1List[2].surName= "f";
*********
m2List[0].id= "1";
m2List[0].email= "aa@gmail.com";
m2List[0].phone= "555";
m2List[1].id= "2";
m2List[1].email= "bb@gmail.com";
m2List[1].phone= "44";
m2List[2].id= "3";
m2List[2].email= "cc@gmail.com";
m2List[2].phone= "77";
Then I want create a new list that contains records from both lists that are matched together with the same id
value. The matched records should be combined into a new object that has the properties from both records.
List<Model1and2> m1Andm2List= new List<Model1and2>();
m1Andm2List[0].id= "1";
m1Andm2List[0].name = "a";
m1Andm2List[0].surName= "b";
m1Andm2List[0].email= "aa@gmail.com";
m1Andm2List[0].phone= "555";
m1Andm2List[1].id= "2";
m1Andm2List[1].name = "c";
m1Andm2List[1].surName= "d";
m1Andm2List[1].email= "bb@gmail.com";
m1Andm2List[1].phone= "44";
Then I want to append that list with all of the records that did not match records in the other list. Because there is no match the properties from the record that wasn't found should be left blank, it should be similar to this:
m1Andm2List[2].id= "3";
m1Andm2List[2].name = "";
m1Andm2List[2].surName= "";
m1Andm2List[2].email= "cc@gmail.com";
m1Andm2List[2].phone= "77";
m1Andm2List[3].id= "5";
m1Andm2List[3].name = "e";
m1Andm2List[3].surName= "f";
m1Andm2List[3].email= "";
m1Andm2List[3].phone= "";
Because m1List
doesn't have a record with id == 3
and m2List
doesn't have a record with id == 5
.
How can I combine two lists like this in C# to produce the expected results?
答案1
得分: 0
你所要求的是这两个集合之间的完全外连接(Full Outer Join)。
从概念上讲,这是三个列表连接在一起的过程:
- 所有在
m1List
和m2List
中都匹配的项。 - 所有在
m1List
中存在但在m2List
中不存在的项。 - 所有在
m2List
中存在但在m1List
中不存在的项。
在跳过所有基本原理的情况下,我们可以使用 LINQ 的左外连接来将第一个和第二个列表合并为一个单一查询,这在这里有文档说明:执行左外连接。第三个查询我们可以称之为“反连接(Anti-Join)”,因为它只需要返回不匹配的项,我们会将这两个表达式的结果连接在一起:
// 完全外连接
var m1Andm2List = m1Andm2_LeftJoin_Query
.Concat(m1Andm2_RightAntiJoin_Query)
.OrderBy(x => x.id)
.ToList();
左外连接
首先让我们讨论左外连接:
// LINQ 查询 - 左外连接 - m1 中的所有项,m2 的默认值
var m1Andm2_LeftJoin_Query = from m1 in m1List
join m2 in m2List on m1.id equals m2.id into merged
from m2 in merged.DefaultIfEmpty()
select new Model1and2
{
id = m1.id,
name = m1.name,
surName = m1.surName,
email = m2 != null ? m2.email : String.Empty,
phone = m2 != null ? m2.phone : String.Empty
};
这段代码之所以有效,是因为特殊函数 .DefaultIfEmpty()
会在无法找到与 m1List
中的行匹配的情况下将一个 null
对象提供给结果。select
语句是一个“投影”,它允许我们同时使用两个表的结果构建一个新对象。如果你不熟悉 LINQ,这等效于以下逻辑:
var m1Andm2_LeftJoin_List = new List<Model1and2>();
foreach (var m1 in m1List)
{
var m2 = m2List.FirstOrDefault(x => x.id == m1.id);
var projection = new Model1and2
{
id = m1.id,
name = m1.name,
surName = m1.surName,
email = String.Empty,
phone = String.Empty
};
if (m2 != null)
{
projection.email = m2.email;
projection.phone = m2.phone;
}
m1Andm2_LeftJoin_List.Add(projection);
}
LINQ 表达式只是一种不同的结构方式,但它将任何过滤和比较逻辑隔离开来,同时将所有内容合并到一个语句中。许多人(特别是熟悉 SQL 风格语法的人)会更快地理解使用 LINQ 表达式的逻辑意图,比起分步骤的程序步骤,将所有内容合并到一个表达式中,未来重构或维护代码更有可能保持一致,减少因遗漏部分逻辑而导致的错误。
- 如果你忘记调用
.Add()
方法来实际将对象添加到列表中,代码看起来正确,但没有任何结果。多年来,我见过太多这种情况了。
反连接(Anti Join)
在 LINQ 中,这不太明显,我们要做的是反转列表,从 m2List
开始,然后左外连接到 m1List
,但要过滤掉在两个列表中都找到匹配项的任何行。在 LINQ 中,我们可以使用一个简单的 where
表达式来做到这一点:
// LINQ 查询 - 右反连接半连接 - 所有不匹配 m1 的 m2,m1 的默认值
var m1Andm2_RightAntiJoin_Query = from m2 in m2List
join m1 in m1List on m2.id equals m1.id into merged
from m1 in merged.DefaultIfEmpty()
where m1 == null
select new Model1and2
{
id = m2.id,
name = String.Empty,
surName = String.Empty,
email = m2.email,
phone = m2.phone
};
你也可以使用更简单易读的非关联表达式,像这样:
var m1Andm2_RightAntiJoin_Query = from m2 in m2List
where !m1List.Any(m1 => m1.id == m2.id)
select new Model1and2
{
id = m2.id,
name = String.Empty,
surName = String.Empty,
email = m2.email,
phone = m2.phone
};
或者甚至使用这个简单的循环:
var m1Andm2_RightAntiJoin_List = new List<Model1and2>();
foreach (var m2 in m2List)
{
var m1 = m1List.FirstOrDefault(x => x.id == m2.id);
if (m1 == null)
{
var projection = new Model1and2
{
id = m2.id,
name = String.Empty,
surName = String.Empty,
email = m2.email,
phone = m2.phone
};
m1Andm2_RightAntiJoin_List.Add(projection);
}
}
对于这种情况,JOIN 语法可能不会总是使查询更易读,但我们仍然使用它,因为它允许我们识别和重复使用在类似逻辑模式之间的常见代码表达式。我们还期望作为开发人员,框架会有一种实现来管理默认情况下由比我们聪明得多的人优化的查找和匹配逻辑。
在选择要实现的语法选
英文:
What you are asking for is a Full Outer Join between these two sets.
Conceptually this is 3 lists that are concatenated together:
- All the items that match in both
m1List
andm2List
- All the items in
m1List
that do not exist inm2List
- All the items in
m2List
that do not exist inm1List
Skipping all the first principals, we can use a LINQ Left Outer join to combine both the first and second lists into a single query, this is documented here: Perform left outer joins. The third query we can refer to as an Anti-Join because it needs to return only the items that do not match, we would concatenate the results from these two expressions together:
// Full Outer Join
var m1Andm2List = m1Andm2_LeftJoin_Query
.Concat(m1Andm2_RightAntiJoin_Query)
.OrderBy(x => x.id)
.ToList();
LEFT OUTER JOIN
Lets talk about the Left outer join first:
// LINQ Query - LEFT OUTER JOIN - All of m1, default values for m2
var m1Andm2_LeftJoin_Query = from m1 in m1List
join m2 in m2List on m1.id equals m2.id into merged
from m2 in merged.DefaultIfEmpty()
select new Model1and2
{
id = m1.id,
name = m1.name,
surName = m1.surName,
email = m2 != null ? m2.email : String.Empty,
phone = m2 != null ? m2.phone : String.Empty
};
This works because the special function .DefaultIfEmpty()
will provide a null
object into the results if no matching row from m2List
can be found. The select
statement is a projection that allows us to constrcut a new object using the results from the two tables at the same time. If you're not into LINQ, this is equivalent logic:
var m1Andm2_LeftJoin_List = new List<Model1and2>();
foreach(m1 in m1List)
{
var m2 = m2List.FirstOrDefault(x => x.id == m1.id);
var projection = new Model1and2
{
id = m1.id,
name = m1.name,
surname = m1.surName,
email = String.Empty,
phone = String.Empty
};
if (m2 != null)
{
projection.email = m2.email;
projection.phone = projection.phone;
}
m1Andm2_LeftJoin_List.Add(projection);
}
The LINQ expression is just a different way to structure and the same intent, but it isolates any filtering and comparison logic while still combining everything into a single statement. Many people (especially those familiar with SQL style syntax) will understand the intent behind your logic with a LINQ expression quicker than the procedural steps. By combining everything into a single expression, future refactoring or maintenance of your code is more likely to keep it together and make less mistakes caused when parts of the logic are omitted.
- If you forgot to call the
.Add()
method to actually put the object on the list, the code would look right, but would not have any results. Over the years I have seen this too many times to count.
Anti Join
In LINQ this is less obvious, what we do is invert the lists, so start with m2List
and LEFT OUTER JOIN into m1List
, but then filter out any rows that did find a match in both lists. In LINQ we do this with a simple where
expression:
// LINQ Query - RIGHT Anti Semi Join - All of m2 that don't match m1, default values for m1
var m1Andm2_RightAntiJoin_Query = from m2 in m2List
join m1 in m1List on m2.id equals m1.id into merged
from m1 in merged.DefaultIfEmpty()
where m1 == null
select new Model1and2
{
id = m2.id,
name = String.Empty,
surName = String.Empty,
email = m2.email,
phone = m2.phone
};
You could also have used a more simple to read non-correlation expression like this:
var m1Andm2_RightAntiJoin_Query = from m2 in m2List
where !m1List.Any(m1 => m1.id == m2.id)
select new Model1and2
{
id = m2.id,
name = String.Empty,
surName = String.Empty,
email = m2.email,
phone = m2.phone
};
or even this simple loop:
var m1Andm2_RightAntiJoin_List = new List<Model1and2>();
foreach(m2 in m2List)
{
var m1 = m1List.FirstOrDefault(x => x.id == m2.id);
if (m1 == null)
{
var projection = new Model1and2
{
id = m2.id,
name = String.Empty,
surname = String.Empty,
email = m2.email,
phone = m2.phone
};
m1Andm2_RightAntiJoin_List.Add(projection);
}
}
The JOIN syntax for this scenario might not make the query always easier to read, but we use it anyway because it allows us to identify and reuse common code expressions between similar logic patterns. We also expect as developers that the framework would have an implementation for managing the lookup and match logic that is optimised by default by people who are far smarter than us.
The final output, after choosing your syntax options to implement, should look like this: https://dotnetfiddle.net/Xw7mvc
id | name | surName | phone | |
---|---|---|---|---|
1 | a | b | aa@gmail.com | 555 |
2 | c | d | bb@gmail.com | 44 |
3 | cc@gmail.com | 77 | ||
5 | e | f |
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论