![](/img/trans.png)
[英]Is there a way to compare two strings in C# and get the differences only?
[英]Compare two LINQ queries and get differences in a datagridview (C#)
以下可能是一種可能的方法(不聲稱這是最好的方法)。
假設你的class實現如下:
public class Item
{
public int Id { get; set; }
public int Weight { get; set; }
public int Phase { get; set; }
}
您可以實施一個比較 class 來比較上一個項目(可能是null
)和當前項目(可能是null
):
public class ItemComparison
{
public int Id { get; init; }
public string Weight => GetComparisonString(_weightPrevious, _weightCurrent);
public string Phase => GetComparisonString(_phasePrevious, _phaseCurrent);
public ItemComparison(Item previousItem, Item currentItem)
{
// TODO Error handling: previousItem and currentItem may
// - both be null
// - both be not null and have different Id values
Id = (previousItem ?? currentItem).Id;
_weightPrevious = previousItem?.Weight;
_weightCurrent = currentItem?.Weight;
_phasePrevious = previousItem?.Phase;
_phaseCurrent = currentItem?.Phase;
}
private int? _weightPrevious;
private int? _weightCurrent;
private int? _phasePrevious;
private int? _phaseCurrent;
private string GetComparisonString(int? previousValue, int? currentValue)
{
if (previousValue == currentValue)
{
return "not changed";
}
return $"{GetStringOrUnknown(previousValue)} --> {GetStringOrUnknown(currentValue)}";
}
private string GetStringOrUnknown(int? value)
{
return value.HasValue ? value.ToString() : "?";
}
}
並按如下方式計算更新的項目、刪除的項目和添加的項目:
//using System.Collections.Generic;
//using System.Linq;
var updatedItems = queryResult1
.Join(queryResult2,
previous => previous.Id,
current => current.Id,
(previous, current) => new ItemComparison(previous, current));
var removedItems = queryResult1
.ExceptBy(queryResult2.Select(qr2 => qr2.Id), qr1 => qr1.Id)
.Select(removed => new ItemComparison(removed, null));
var addedItems = queryResult2
.ExceptBy(queryResult1.Select(qr1 => qr1.Id), qr2 => qr2.Id)
.Select(added => new ItemComparison(null, added));
最后,可以連接項目比較以產生最終比較結果:
List<ItemComparison> comparisonResult = updatedItems
.Concat(removedItems)
.Concat(addedItems)
.OrderBy(item => item.Id)
.ToList();
然后可以使用comparisonResult
來填充您的DataGridView
。
示例小提琴在這里。
假設 ID 永遠不會改變,我的建議是對 ID 進行完全外部連接
las, class Enumerable沒有用於完全外部連接的擴展方法。 幸運的是,創建一個相當容易。 如果您不熟悉擴展方法,請考慮閱讀Extension Methods Demystified (另一個使用方法語法而不是查詢語法的好理由)
public static IEnumerable<TResult> FullOuterJoin<T1, T2, TKey, TResult>(
this IEnumerable<T1> sequence1,
IEnumerable<T2> sequence2,
Func<T1, TKey> joinKey1Selector,
Func<T2, TKey> joinKey2Selector,
Func<T1, T2, TKey, TResult> resultSelector,
IEqualityComparer<TKey> keyComparer)
{
// TODO: implement
}
就像許多 LINQ 方法一樣,您可以通過編寫多個重載來幫助您的方法的用戶,例如,一個沒有keyComparer
的重載:
public static IEnumerable<TResult> FullOuterJoin<T1, T2, TKey, TResult>(
this IEnumerable<T1> sequence1,
IEnumerable<T2> sequence2,
Func<T1, TKey> joinKey1Selector,
Func<T2, TKey> joinKey2Selector,
Func<T1, T2, Tkey, TResult> resultSelector)
{
return FullOuterJoin(sequence1, sequence2,
joinKey1Selector, joinKey2Selector,
resultSelector, null);
}
用法如下:
class QueryResult
{
public int Id {get; set;}
public decimal Weight {get; set;} // maybe other type
public int Phase {get; set;}
}
IEnumerable<QueryResult> query1 = ...
IEnumerable<QueryResult> query2 = ...
// full outer join query1 and query 2 on Id:
var fullOuterJoin = query1.FullOuterJoin(query2,
queryResult => queryResult.Id, // from every element from query1 take the Id
queryResult => queryResult.Id, // from every element from query2 take the Id
// parameter resultSelector: from every T1 and its matching T2 make one new
// note: T1 or T2 can be null (but not both)
(x, y, key) => new
{
Id = key,
Added = T1 == null,
Removed = T2 == null,
Changed = T1 != T2,
Original = T1,
Current = T2,
};
我認為將屬性 Added / Removed / Changed 更改為枚舉會更整潔。 為此創建一個方法:
enum ChangeState {Unchanged, Added, Removed, Changed};
ChangeState DetectChange<T>(T x, T y)
{
return DetectChange(x, y, null); // call the overload with comparer
}
ChangeState DetectChange<T>(T x, T y, IEqualityComparer<T> comparer)
{
if (comparer == null) comparer = EqualityComparer<T>.Default;
if (comparer.Equals(x, y)) return ChangeState.Unchanged;
if (x == null) return ChangeState.Added; // because y not null
if (y == null) return ChangeState.Removed; // because x not null
return ChangeState.Changed;
}
參數 resultSelector 將是這樣的:
(x, y, key) => new
{
Id = key,
ChangeState = DetectChange(x, y),
...
實現相當簡單:
.
public static IEnumerable<TResult> FullOuterJoin<Tx, Ty, TKey, TResult>(
this IEnumerable<Tx> sequenceX,
IEnumerable<Ty> sequenceY,
Func<Tx, TKey> xKeySelector,
Func<Ty, TKey> yKeySelector,
Func<Tx, Ty, TKey, TResult> resultSelector,
IEqualityComparer<TKey> keyComparer)
{
// TODO: throw exception if any of sequenceX, sequenceY,
// KeySelectors or resultSelector equal null
// if keyComparer equals null, use default comparison technique
if (keyComparer == null) keyComparer = EqualityComparer<TKey>.Default;
// create two lookupTables:
IDictionary<TKey, Tx> dictX = sequence1.ToDictionary(x => joinKey1Selector(x), keyComparer);
IDictionary<TKey, Ty> dictY = sequence1.ToDictionary(y => joinKey2Selector(y), keyComparer);
// get all used Tkey:
IEnumerable<TKey> keysX= dictX.Select(x => x.Key);
IEnumerable<TKey> keysY= dictY.Select(y => y.Key);
IEnumerable<TKey> allUsedKeys = keysX.Union(keyY, keyComparer);
// for every used key, get the x and the y and return a result
foreach(TKey key in allUsedKeys)
{
dictX.TryGetValue(key, out Tx foundX); // null if not found
dictY.TryGetValue(key, out Ty foundY);
TResult result = resultSelector(foundX, foundY, key);
yield return result;
}
}
問題:完全外部連接的簡單擴展方法不適用於 IQueryable,僅適用於 IEnumerable。
如果您真的希望您的數據庫管理系統將完整的外部連接作為可查詢的,那么在將數據返回到您的本地進程之前,您必須創建一個以IQueryable<...>
作為輸入的擴展方法。 也許這篇關於Left Outer Join as IQueryable的文章是一個很好的起點。
如果我查看建議的 Left Outer Join 的長度,我認為它不會非常有效。 考慮向 DbContext 添加一個執行完整外部聯接的方法,如 SQL 語句。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.