我已经考虑过如何通过推出自己的解决方案来解决这个问题,但我想知道.NET是否已经具备了我想要实现的功能 - 如果是这样,我宁愿使用某些东西内置。
假设我有Widget
个对象的两个实例,我们称之为PartA
和PartB
。来自每个服务的信息来自两个不同的Web服务,但两者都具有匹配的ID。
PartA
{
ID: 19,
name: "Percy",
taste: "",
colour: "Blue",
shape: "",
same_same: "but different"
}
PartB
{
ID: 19,
name: "",
taste: "Sweet",
colour: "",
shape: "Hexagon",
same_same: "but not the same"
}
我想合并它们以创建以下内容:
Result
{
ID: 19,
name: "Percy",
taste: "Sweet",
colour: "Blue",
shape: "Hexagon",
same_same: "but different"
}
注意same_same
的值在每个值之间有何不同,但我们认为PartA是主节点,因此结果保留值but different
。
现在让问题复杂化:
假设我们有两个列表:
List<Widget> PartA = getPartA();
List<Widget> PartB = getPartB();
现在这里有一些伪代码描述了我想要做的事情:
List<Widget> Result = PartA.MergeWith(PartB).MergeObjectsOn(Widget.ID).toList();
答案 0 :(得分:13)
您可以编写自己的扩展方法,如下所示:
static class Extensions
{
public static IEnumerable<T> MergeWith<T>(this IEnumerable<T> source, IEnumerable<T> other) where T : ICanMerge
{
var otherItems = other.ToDictionary(x => x.Key);
foreach (var item in source)
{
yield return (T)item.MergeWith(otherItems[item.Key]);
}
}
public static string AsNullIfEmpty(this string s)
{
if (string.IsNullOrEmpty(s))
return null;
else
return s;
}
}
ICanMerge
之类的地方:
public interface ICanMerge
{
object Key { get; }
ICanMerge MergeWith(ICanMerge other);
}
已实施,例如像:
public class Widget : ICanMerge
{
object ICanMerge.Key { get { return this.ID; } }
int ID {get;set;}
string taste {get;set;}
public ICanMerge MergeWith(ICanMerge other)
{
var merged = new Widget();
var otherWidget = (Widget)other;
merged.taste = this.taste.AsNullIfEmpty() ?? otherWidget.taste;
//...
return merged;
}
}
然后它就像PartA.MergeWith(PartB).ToList()
一样简单。
答案 1 :(得分:2)
如果您的列表是一对一的(即相同数量的项目,并且PartA列表中的每个项目在PartB列表中都匹配),那么我会考虑Zip扩展方法。请注意,Zip实际上并不要求每个列表具有相同数量的项目。但是,如果你不能依赖&#34;配对&#34;具有匹配ID的项目,然后我的简单方法不起作用。
你可以这样做:
var alist = GetPartAWidgets().OrderBy(w => w.ID);
var blist = GetPartBWidgets().OrderBy(w => w.ID);
var merged = alist.Zip(blist, (a,b) => new Widget()
{
ID = a.ID,
Name = string.IsNullOrEmpty(a.Name) ? b.Name : a.Name,
//etc.
});
如果你希望你的linq看起来更干净,你可以在函数或扩展方法中封装单个Widget合并逻辑,并使用它而不是内联委托。
答案 2 :(得分:0)
public interface IMerge<out T>
{
IEnumerable<IMergeMatched<T>> Matched();
IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate);
IEnumerable<T> NotMatchedBySource();
IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate);
IEnumerable<T> NotMatchedByTarget();
IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate);
}
public interface IMergeMatched<out T>
{
T Source { get; }
T Target { get; }
}
public static class Enumerable
{
public static IMerge<TSource> Merge<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> target,
Func<TSource, TSource, bool> predicate)
{
return new Merge<TSource>(source, target, predicate);
}
}
public class Merge<T> : IMerge<T>
{
private readonly Func<T, T, bool> _predicate;
private readonly IEnumerable<T> _source;
private readonly IEnumerable<T> _target;
private IEnumerable<IMergeMatched<T>> _matcheds;
private IEnumerable<T> _notMatchedBySource;
private IEnumerable<T> _notMatchedByTarget;
public Merge(IEnumerable<T> source, IEnumerable<T> taget, Func<T, T, bool> predicate)
{
_source = source;
_target = taget;
_predicate = predicate;
}
public IEnumerable<IMergeMatched<T>> Matched()
{
if (_matcheds == null)
{
Analize();
}
return _matcheds;
}
public IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate)
{
return Matched()
.Where(t => predicate.Invoke(t.Source, t.Target))
.ToArray();
}
public IEnumerable<T> NotMatchedBySource()
{
if (_notMatchedBySource == null)
{
Analize();
}
return _notMatchedBySource;
}
public IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate)
{
return NotMatchedBySource()
.Where(predicate)
.ToArray();
}
public IEnumerable<T> NotMatchedByTarget()
{
if (_notMatchedByTarget == null)
{
Analize();
}
return _notMatchedByTarget;
}
public IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate)
{
return NotMatchedByTarget()
.Where(predicate)
.ToArray();
}
private void Analize()
{
var macheds = new List<MergeMached<T>>();
var notMachedBySource = new List<T>(_source);
var notMachedByTarget = new List<T>(_target);
foreach (var source in _source)
{
foreach (var target in _target)
{
var macth = _predicate.Invoke(source, target);
if (!macth) continue;
macheds.Add(new MergeMached<T>(source, target));
notMachedBySource.Remove(source);
notMachedByTarget.Remove(target);
}
}
_matcheds = macheds.ToArray();
_notMatchedBySource = notMachedBySource.ToArray();
_notMatchedByTarget = notMachedByTarget.ToArray();
}
}
public class MergeMached<T> : IMergeMatched<T>
{
public MergeMached(T source, T target)
{
Source = source;
Target = target;
}
public T Source { get; private set; }
public T Target { get; private set; }
}
如何使用?
var source = new List<MediaFolder>
{
new MediaFolder
{
Id = "Id1",
Name = "Name1",
Path = "Path1"
},
new MediaFolder
{
Id = "Id2",
Name = "Name2",
Path = "Path2"
},
new MediaFolder
{
Id = "Id3",
Name = "Name3",
Path = "Path3"
},
new MediaFolder
{
Id = "Id4",
Name = "Name4",
Path = "Path4"
},
new MediaFolder
{
Id = "Id5",
Name = "Name5",
Path = "Path5"
},
new MediaFolder
{
Id = "Id6",
Name = "Name6",
Path = "Path6"
}
};
var target = new List<MediaFolder>
{
new MediaFolder
{
Id = "Id1",
Name = "Actualizado en el objeto",
Path = "Path1"
},
//Id2 eliminado
new MediaFolder
{
Id = "Id3",
Name = "Name3",
Path = "Actualizado tambien"
},
new MediaFolder
{
Id = "Id4",
Name = "Name4",
Path = "Path4"
},
new MediaFolder
{
Id = "Id5",
Name = "Name5",
Path = "Path5"
},
new MediaFolder
{
Id = "Id6",
Name = "Name6",
Path = "Path6"
},
new MediaFolder
{
Id = "Id7",
Name = "Nuevo Item 7",
Path = "Nuevo Item 7"
}
};
var merge = source.Merge(target, (x, y) => x.Id == y.Id);
var toUpdate = merge.Matched((x, y) => x.Name != y.Name | x.Path != y.Path)
.ToArray();
var toDelete = merge.NotMatchedBySource();
var toInsert = merge.NotMatchedByTarget();
Assert.AreEqual(2, toUpdate.Count());
Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id1" & x.Target.Id == "Id1") > 0);
Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id3" & x.Target.Id == "Id3") > 0);
Assert.AreEqual("Id7", toInsert.First().Id);
Assert.AreEqual("Id2", toDelete.First().Id);