Merge two list based on Id [duplicate] - c#

I am taking a union of two lists using Linq to Sql. Using List1 and List2:
var tr = List1.Union(List2).ToList();
Union works fine, but the problem is it is checking each column and removes some of the rows that I want. So I was wondering if there is a a way I can perform a union based on one column only, like let's say id, of each list?
Something Like:
var t = List1.id.Union(List2.id).ToList();
This doesn't work, but I was wondering if there is a way to do this, either with LINQ or T-SQL

You should use this Union() overload (with a custom equality comparer) , or something like this:
list1.Concat(list2).GroupBy(x => x.DateProperty).Select(m => m.First());
The first solution is certainly more efficient.

Sure, you need a custom IEqualityComparer with Union. I have one that's really dynamic, big block of code incoming though:
public class PropertyEqualityComparer<TObject, TProperty>
: IEqualityComparer<TObject>
{
Func<TObject, TProperty> _selector;
IEqualityComparer<TProperty> _internalComparer;
public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector,
IEqualityComparer<TProperty> innerEqualityComparer = null)
{
_selector = propertySelector;
_internalComparer = innerEqualityComparer;
}
public int GetHashCode(TObject obj)
{
return _selector(obj).GetHashCode();
}
public bool Equals(TObject x, TObject y)
{
IEqualityComparer<TProperty> comparer =
_internalComparer ?? EqualityComparer<TProperty>.Default;
return comparer.Equals(_selector(x), _selector(y));
}
}
public static class PropertyEqualityComparer
{
public static PropertyEqualityComparer<TObject, TProperty>
GetNew<TObject, TProperty>(Func<TObject, TProperty> propertySelector)
{
return new PropertyEqualityComparer<TObject, TProperty>
(propertySelector);
}
public static PropertyEqualityComparer<TObject, TProperty>
GetNew<TObject, TProperty>
(Func<TObject, TProperty> propertySelector,
IEqualityComparer<TProperty> comparer)
{
return new PropertyEqualityComparer<TObject, TProperty>
(propertySelector, comparer);
}
}
Now, all you need to do is call Union with that equality comparer (instantiated with a lambda that fits your circumstance):
var tr = List1.Union(List2, PropertyEqualityComparer.GetNew(n => n.Id)).ToList();

try somthing this
var List3 = List1.Join(
List2,
l1 => l1.Id,
l2 => l2.Id,
(l1, l2) => new Model
{
Id = l1.Id,
Val1 = l1.Val1 or other,
Val2 = l2.Val2 or other
});
for more details you can show your model

Try this:
var merged = new List<Person>(list1);
merged.AddRange(list2.Where(p2 =>
list1.All(p1 => p1.Id != p2.Id)));

Related

Refactor LINQ method to introduce Lamba function as a parameter

I have the current code, it uses LINQ to select validated ids from an id list:
var ids = idList
.Select(page => page.PageId?? -1)
.Where(id => id > -1)
.Distinct()
.ToArray();
In my application I use this code fragment a lot and I was wondering if I could wrap this piece of code in another function and pass the two lambda expressions as parameters to shorten it.
Something like:
int[] pageIds = getIdsOfPages(page => page.Id ?? -1, id => id > -1);
Could I implement this and How would I go about this?
I have seen lambda expressions and functions but I've had no luck so far. Expression<Func<Object, Bool>> & Func<Object, Bool>.
P.s the second parameter id > -1. That would be a constant.. only the first parameter would change.
Thanks in advance.
Edit:
Here's my current implementation:
Calling method:
public int[] getPageIds(List<PageObject> pageHeadersList){
IQueryable<DataModel.PageHeader> list = pageHeadersList.AsEnumerable().AsQueryable();
int[] pageIds = list.GetIds(x => x.PageId ?? -1);
return pageIds;
}
public static IQueryable<T> ExecuteWhereClause<T>(IQueryable<T> inputQuery, string paramName, object typedValue)
{
ParameterExpression parameter = Expression.Parameter(typeof(T));
var condition =
Expression.Lambda<Func<T, bool>>(
Expression.GreaterThan(
Expression.Property(parameter, paramName),
Expression.Constant(typedValue)
),
parameter
);
return inputQuery.Where(condition);
}
public static int[] GetIds<TSource, TKey>(this IQueryable<TSource> list, Func<TSource, TKey> keySelector)
{
var newList = list.Select(keySelector)
.AsQueryable();
var newListAfterWhere = ExecuteWhereClause(newList , "Id", -1);
return newListAfterWhere .Cast<int>().ToArray();
}
actually the Select and the Where tell you what kind of input functions they need. The answer is already in your question post.
For Where you need
Expression<Func<int, bool>> filter
and for Select :
Expression<Func<YourType, int>> selector
Here is an example with a custom class:
public static class Extensions
{
public static int[] getIdsOfPages<TSource>(this IQueryable<TSource> list, Expression<Func<TSource, int>> select, Expression<Func<int, bool>> filter)
{
return list
.Select(select)
.Where(filter)
.Distinct()
.ToArray();
}
}
You would call it like this:
int[] pageIds = idList.AsQueryable().getIdsOfPages(page => page.PageId ?? -1, id => id > -1);
public static List<MyClass> idList = new List<MyClass>();
public class MyClass
{
public int? PageId { get; set; }
}
Although it looks for me to be more efficient, if you would first filter and then select. You would modify the selector function a little:
public static int[] getIdsOfPagesFilterFirst<TSource>(
this IQueryable<TSource> list,
Expression<Func<TSource, int>> select,
Expression<Func<TSource, bool>> filter)
{
return list.Where(filter).Select(select).Distinct().ToArray();
}
And call it like this:
int[] pageIds = idList.AsQueryable().getIdsOfPagesFilterFirst(page => page.PageId.Value , page => page.PageId.HasValue);
Here's one I created earlier which allows Where, OrderBy and Select where T = YourClass
public IQueryable<T> Get(
Expression<Func<T, int>> select,
Expression<Func<T, bool>> where,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy)
{
var databaseSet = this.Context.Set<T>();
var query = (IQueryable<T>) databaseSet;
if (filter != null)
{
query = query.Where(where);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (select != null)
{
query = query.Select(select);
}
return query;
}

how to select common elements according to the value of a property?

If I want the common elements in two list, I can use the intersect function:
var listC = listA.Intersect(listB);
But this compare objects. If the lists have objects of type Persons and I would like to get the persons with the same name for example, how could I do that? Where I set the condition of the name property?
Thanks.
Pass it a custom IEqualityComparer<T>.
First, make a class that implements that interface:
public class PersonNameEqualityComparer:IEqualityComparer<Person>
{
public int GetHashCode (Person obj)
{
return obj.Name.GetHashcode ();
}
public bool Equals (Person x, Person y)
{
return x.Name == y.Name;
}
}
Then, all you need to do is pass an instance of that IEqualityComparer to the intersect method.
var result = listA.Intersect(listB, new PersonNameEqualityComparer());
You could extend this to any object and any property, using generics and lambdas:
public class PropertyEqualityComparer<TObject, TProperty> : IEqualityComparer<TObject>
{
Func<TObject, TProperty> _selector;
IEqualityComparer<TProperty> _internalComparer;
public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector, IEqualityComparer<TProperty> innerEqualityComparer = null)
{
_selector = propertySelector;
_internalComparer = innerEqualityComparer;
}
public int GetHashCode(TObject obj)
{
return _selector(obj).GetHashCode();
}
public bool Equals(TObject x, TObject y)
{
IEqualityComparer<TProperty> comparer = _internalComparer ?? EqualityComparer<TProperty>.Default;
return comparer.Equals(_selector(x), _selector(y));
}
}
You could then just use it like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Name));
or like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Age));
and so on.

Taking union of two lists based on column

I am taking a union of two lists using Linq to Sql. Using List1 and List2:
var tr = List1.Union(List2).ToList();
Union works fine, but the problem is it is checking each column and removes some of the rows that I want. So I was wondering if there is a a way I can perform a union based on one column only, like let's say id, of each list?
Something Like:
var t = List1.id.Union(List2.id).ToList();
This doesn't work, but I was wondering if there is a way to do this, either with LINQ or T-SQL
You should use this Union() overload (with a custom equality comparer) , or something like this:
list1.Concat(list2).GroupBy(x => x.DateProperty).Select(m => m.First());
The first solution is certainly more efficient.
Sure, you need a custom IEqualityComparer with Union. I have one that's really dynamic, big block of code incoming though:
public class PropertyEqualityComparer<TObject, TProperty>
: IEqualityComparer<TObject>
{
Func<TObject, TProperty> _selector;
IEqualityComparer<TProperty> _internalComparer;
public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector,
IEqualityComparer<TProperty> innerEqualityComparer = null)
{
_selector = propertySelector;
_internalComparer = innerEqualityComparer;
}
public int GetHashCode(TObject obj)
{
return _selector(obj).GetHashCode();
}
public bool Equals(TObject x, TObject y)
{
IEqualityComparer<TProperty> comparer =
_internalComparer ?? EqualityComparer<TProperty>.Default;
return comparer.Equals(_selector(x), _selector(y));
}
}
public static class PropertyEqualityComparer
{
public static PropertyEqualityComparer<TObject, TProperty>
GetNew<TObject, TProperty>(Func<TObject, TProperty> propertySelector)
{
return new PropertyEqualityComparer<TObject, TProperty>
(propertySelector);
}
public static PropertyEqualityComparer<TObject, TProperty>
GetNew<TObject, TProperty>
(Func<TObject, TProperty> propertySelector,
IEqualityComparer<TProperty> comparer)
{
return new PropertyEqualityComparer<TObject, TProperty>
(propertySelector, comparer);
}
}
Now, all you need to do is call Union with that equality comparer (instantiated with a lambda that fits your circumstance):
var tr = List1.Union(List2, PropertyEqualityComparer.GetNew(n => n.Id)).ToList();
try somthing this
var List3 = List1.Join(
List2,
l1 => l1.Id,
l2 => l2.Id,
(l1, l2) => new Model
{
Id = l1.Id,
Val1 = l1.Val1 or other,
Val2 = l2.Val2 or other
});
for more details you can show your model
Try this:
var merged = new List<Person>(list1);
merged.AddRange(list2.Where(p2 =>
list1.All(p1 => p1.Id != p2.Id)));

LINQ: Use .Except() on collections of different types by making them convertible/comparable?

Given two lists of different types, is it possible to make those types convertible between or comparable to each other (eg with a TypeConverter or similar) so that a LINQ query can compare them? I've seen other similar questions on SO but nothing that points to making the types convertible between each other to solve the problem.
Collection Types:
public class Data
{
public int ID { get; set; }
}
public class ViewModel
{
private Data _data;
public ViewModel(Data data)
{
_data = data;
}
}
Desired usage:
public void DoMerge(ObservableCollection<ViewModel> destination, IEnumerable<Data> data)
{
// 1. Find items in data that don't already exist in destination
var newData = destination.Except(data);
// ...
}
It would seem logical that since I know how to compare an instance of ViewModel to an instance of Data I should be able to provide some comparison logic that LINQ would then use for queries like .Except(). Is this possible?
I assume that providing a projection from Data to ViewModel is problematic, so I'm offering another solution in addition to Jason's.
Except uses a hash set (if I recall correctly), so you can get similar performance by creating your own hashset. I'm also assuming that you are identifying Data objects as equal when their IDs are equal.
var oldIDs = new HashSet<int>(data.Select(d => d.ID));
var newData = destination.Where(vm => !oldIDs.Contains(vm.Data.ID));
You might have another use for a collection of "oldData" elsewhere in the method, in which case, you would want to do this instead. Either implement IEquatable<Data> on your data class, or create a custom IEqualityComparer<Data> for the hash set:
var oldData = new HashSet<Data>(data);
//or: var oldData = new HashSet<Data>(data, new DataEqualityComparer());
var newData = destination.Where(vm => !oldData.Contains(vm.Data));
I know this is late but there is a simpler syntax using Func that eliminates the need for a comparer.
public static class LinqExtensions
{
public static IEnumerable<TSource> Except<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.Count(y => comparer(x, y)) == 0);
}
public static IEnumerable<TSource> Contains<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.FirstOrDefault(y => comparer(x, y)) != null);
}
public static IEnumerable<TSource> Intersect<TSource, VSource>(this IEnumerable<TSource> first, IEnumerable<VSource> second, Func<TSource, VSource, bool> comparer)
{
return first.Where(x => second.Count(y => comparer(x, y)) == 1);
}
}
so with lists of class Foo and Bar
public class Bar
{
public int Id { get; set; }
public string OtherBar { get; set; }
}
public class Foo
{
public int Id { get; set; }
public string OtherFoo { get; set; }
}
one can run Linq statements like
var fooExceptBar = fooList.Except(barList, (f, b) => f.Id == b.Id);
var barExceptFoo = barList.Except(fooList, (b, f) => b.OtherBar == f.OtherFoo);
it's basically a slight variation on above but seems cleaner to me.
If you use this :
var newData = destination.Except(data.Select(x => f(x)));
You have to project 'data' to same type contained in 'destination', but using the code below you could get rid of this limitation :
//Here is how you can compare two different sets.
class A { public string Bar { get; set; } }
class B { public string Foo { get; set; } }
IEnumerable<A> setOfA = new A[] { /*...*/ };
IEnumerable<B> setOfB = new B[] { /*...*/ };
var subSetOfA1 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo);
//alternatively you can do it with a custom EqualityComparer, if your not case sensitive for instance.
var subSetOfA2 = setOfA.Except(setOfB, a => a.Bar, b => b.Foo, StringComparer.OrdinalIgnoreCase);
//Here is the extension class definition allowing you to use the code above
public static class IEnumerableExtension
{
public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect)
{
return Except(first, second, firstSelect, secondSelect, EqualityComparer<TCompared>.Default);
}
public static IEnumerable<TFirst> Except<TFirst, TSecond, TCompared>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect,
IEqualityComparer<TCompared> comparer)
{
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
return ExceptIterator<TFirst, TSecond, TCompared>(first, second, firstSelect, secondSelect, comparer);
}
private static IEnumerable<TFirst> ExceptIterator<TFirst, TSecond, TCompared>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TCompared> firstSelect,
Func<TSecond, TCompared> secondSelect,
IEqualityComparer<TCompared> comparer)
{
HashSet<TCompared> set = new HashSet<TCompared>(second.Select(secondSelect), comparer);
foreach (TFirst tSource1 in first)
if (set.Add(firstSelect(tSource1)))
yield return tSource1;
}
}
Some may argue that's memory inefficient due to the use of an HashSet. But actually the Enumerable.Except method of the framework is doing the same with a similar internal class called 'Set' (I took a look by decompiling).
Your best bet is to provide a projection from Data to ViewModel so that you can say
var newData = destination.Except(data.Select(x => f(x)));
where f maps Data to ViewModel. You will need a IEqualityComparer<Data> too.

LINQ: Sorting on many columns dynamically

I have a query of IQueryable and want to apply sorting to it dynamically, sorting can be on many columns (asc or desc). I've written the following generic function:
private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
{
if (order == SortOrder.Ascending)
{
{
return query.OrderBy<T, U>(predicate);
}
}
else
{
{
return query.OrderByDescending<T, U>(predicate);
}
}
}
SortOrder is my simple enum with 2 values: Ascending and Descending
Then I call this function in a loop, for each column that user requested sorting. However I've noticed it fails because it always sorts on the last column used, ignoring the other ones.
Then I found there's a 'ThenBy' method on IOrderedQueryable so the valid usage is:
var q = db.MyType.OrderBy(x=>x.Col1).ThenBy(y=>y.Col2); //etc.
But how can I make it generic? I tried to test if the query is IOrderedQueryable but it seems always to be true even if it's simplest var q = from x in db.MyType select x
I have no clue why it was designed like this. What's wrong with:
var q = db.MyType.OrderBy(x=>x.Col1).OrderBy(y=>y.Col2); //etc.
it's so much intuitive
You just need to check if the query is already ordered :
private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
{
var ordered = query as IOrderedQueryable<T>;
if (order == SortOrder.Ascending)
{
if (ordered != null)
return ordered.ThenBy(predicate);
return query.OrderBy(predicate);
}
else
{
if (ordered != null)
return ordered.ThenByDescending(predicate);
return query.OrderByDescending(predicate);
}
}
How about just making the first OrderBy static, then always ThenBy?
OrderColumn[] columnsToOrderby = getColumnsToOrderby();
IQueryable<T> data = getData();
if(!columnToOrderBy.Any()) { }
else
{
OrderColumn firstColumn = columnsToOrderBy[0];
IOrderedEnumerable<T> orderedData =
firstColumn.Ascending
? data.OrderBy(predicate)
: data.OrderByDescending(predicate);
for (int i = 1; i < columnsToOrderBy.Length; i++)
{
OrderColumn column = columnsToOrderBy[i];
orderedData =
column.Ascending
? orderedData.ThenBy(predicate)
: orderedData.ThenByDescending(predicate);
}
}
Total guess, but can you do something like this?
query.OrderBy(x => 1).ThenBy<T,U>(predicate)
Any syntax errors aside, the idea is to do an OrderBy() that doesn't affect anything, then do the real work in the .ThenBy() method call
I would write a wrapper around and internally use linq extension methods.
var resultList = presentList
.MyOrderBy(x => x.Something)
.MyOrderBY(y => y.SomethingElse)
.MyOrderByDesc(z => z.AnotherThing)
public IQueryable<T> MyOrderBy(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {
return (prevList is IOrderedQueryable<T>)
? query.ThenBy(predicate)
: query.OrderBy(predicate);
}
public IQueryable<T> MyOrderByDesc(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {
return (prevList is IOrderedQueryable<T>)
? query.ThenByDescending(predicate)
: query.OrderByDescending(predicate);
}
PS: I didn't test the code
Extension to dynamic multi-order:
public static class DynamicExtentions
{
public static IEnumerable<T> DynamicOrder<T>(this IEnumerable<T> data, string[] orderings) where T : class
{
var orderedData = data.OrderBy(x => x.GetPropertyDynamic(orderings.First()));
foreach (var nextOrder in orderings.Skip(1))
{
orderedData = orderedData.ThenBy(x => x.GetPropertyDynamic(nextOrder));
}
return orderedData;
}
public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
{
var param = Expression.Parameter(typeof(Tobj), "value");
var getter = Expression.Property(param, propertyName);
var boxer = Expression.TypeAs(getter, typeof(object));
var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();
return getPropValue(self);
}
}
Example:
var q =(myItemsToSort.Order(["Col1","Col2"]);
Note: not sure any IQueryable provider can translate this

Categories