LINQ to Entities .Contains for a tuple (2 foreign keys) - c#

Actually, a colleague of mine asked me this question and I haven't been able to come up with an answer. Here goes.
Given an entity with 2 foreign keys, say
public class MyTable
{
public int Key1 { get; set; }
public int Key2 { get; set; }
}
and 2 lists
public ICollection List1 => new List<int> { 1, 2, 3 };
public ICollection List2 => new List<int> { 4, 5, 6 };
he needs to query for all records where Key1 matches the value from List1 and Key2 matches the value from List2, e.g.
Key1 == 1 && Key2 == 4
that is, he wants to check for any given tuple from List1 and List2, (1, 4), (2, 5) and (3, 6).
Is there a straightforward way in EF to do this?

You can make a for loop to capture some local variable (in each loop) in the Where and use Concat (or Union - maybe with worse performance) to sum up all the result like this:
IQueryable<MyTable> q = null;
//suppose 2 lists have the same count
for(var i = 0; i < list1.Count; i++){
var k1 = list1[i];
var k2 = list2[i];
var s = context.myTables.Where(e => e.Key1 == k1 && e.Key2 == k2);
q = q == null ? s : q.Concat(s);
}
//materialize the result
var result = q.ToList();
NOTE: we can use Concat here because each sub-result should be unique (based on searching the keys). It surely has better performance than Union (ensuring uniqueness while we already know the sub-results are all unique beforehand - so it's unnecessary).
If you have a list of int (just integral numeric), you can also pair the keys into underscore separated string and use Contains normally like this:
var stringKeys = list1.Select((e,i) => e + "_" + list2[i]).ToList();
var result = context.myTables.Where(e => stringKeys.Contains(e.Key1 + "_" + e.Key2))
.ToList();
Building an Expression tree is also another approach but it's more complicated while I'm not sure if it has better performance.

Try this:
static IQueryable<TSource> WhereIn(this Table<TSource> table, List<object[]> list) where TSource : class
{
var query = table.AsQueryable();
foreach (object[] item in list)
{
Expression<Func<TSource, bool>> expr = WhereInExpression(item);
query = query.Where(expr);
}
return query;
}
static Expression<Func<TSource, bool>> WhereInExpression<TSource>(object[] item)
{
ParameterExpression parameterItem = Expression.Parameter(typeof(TSource), "expr");
BinaryExpression filter1 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key1"),
Expression.Constant(item[0]));
BinaryExpression filter2 = Expression.Equal(LambdaExpression.PropertyOrField(parameterItem, "Key2"),
Expression.Constant(item[1]));
BinaryExpression filter = LambdaExpression.And(filter1, filter2);
var expr = Expression.Lambda<Func<TSource, bool>>(filter, new ParameterExpression[] { parameterItem });
expr.Compile();
return expr;
}
Usage:
List<object[]> list = new List<object[]>() { new object[] { 1, 100 }, new object[] { 1, 101 }, new object[] { 2, 100 } };
var result = db.MyTable.WhereIn<MyTable>(list);

Confirmed to work with Entity framework
var tuples = List1.Cast<int>().Zip(List2.Cast<int>(), (l, r) => new { l, r });
var results = Orders.Where(o =>
tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo })
);
Or simpler, if you define your lists as ICollection<int> or IList<int> (etc...):
var tuples = List1.Zip(List2, (l, r) => new { l, r });
var results = Orders.Where(o =>
tuples.Contains(new { l = (int)o.KeyOne, r = (int)o.KeyTwo })
);

Fiddle here : https://dotnetfiddle.net/YyyZBY
var List_1 = new List<int> { 1, 2, 3 };
var List_2 = new List<int> { 4, 5, 6 };
var TargetList = new List<MyTable>();
var index1=0;
var List1 = List_1.Select(x=>new { ind=index1++,value=x });
var index2=0;
var List2 = List_2.Select(x=>new { ind=index2++,value=x });
var values = from l1 in List1
join l2 in List2 on l1.ind equals l2.ind
select new {value1=l1.value,value2=l2.value };
var result = TargetList.Where(x=>values.Any(y=>y.value1==x.Key1&&y.value2==x.Key2));

Related

Entity framework core add multible where as OR

In EF core I have a list of composite Id's, and I then want to have of those ids from the database.
var crits = new List<MyCrit>()
{
new MyCrit() {Key1 = "A", Key2 = 3},
new MyCrit() {Key1 = "B", Key2 = 4}
};
it should end up with SQL like this:
select * from MyTable where (Key1="A" and Key2 = 3) or (Key1="B" and Key2 = 4)
I have configured the table to have correct setup, but I cannot get the OR.
Here is my code:
var query = _db.MyTable.AsQueryable();
foreach (var crit in crits)
{
query = query.Where(m => m.Key1 == crit.Key1 && m.Key2 == crit.Key2);
}
Unfortunatly that gives me this SQL:
select * from MyTable where (Key1="A" and Key2 = 3) and (Key1="B" and Key2 = 4)
I can't figure out how to add inside the loop so it becomes OR.
Use FilterByItems extension and you can simplify your query to the following:
var query = _db.MyTable
.FilterByItems(crits, (m, crit) => m.Key1 == crit.Key1 && m.Key2 == crit.Key2, true);
I have some hope this should work - just Concat the queries together. It should only result in a single query against the database.
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var crits = new List<MyCrit>()
{
new MyCrit() {Key1 = "A", Key2 = 3},
new MyCrit() {Key1 = "A", Key2 = 4}
};
// _db.Table.AsQueryable()
var table = Enumerable.Empty<DbValue>().AsQueryable();
// Could use a foreach over crits with an initial value of Enumerable.Empty<DbValue>().AsQueryable()
var filtered = crits.Select(x => table.Where(y => y.Key1 == x.Key1 && y.Key2 == x.Key2))
.Aggregate(Enumerable.Empty<DbValue>().AsQueryable(), (x, y) => x.Concat(y));
}
}
public class MyCrit
{
public string Key1;
public int Key2;
}
public class DbValue
{
public string Key1;
public int Key2;
public string OtherData;
}
Maybe something like this? (I wrote it from memory and fast so something can work wrong...)
UPDATE
Now I could check it and fix and now should work
Expression CreateBasicExpression(ParameterExpression parameterExpression1, string crit)
{
var rightPart = Expression.Equal(Expression.Property(parameterExpression1, "Name"), Expression.Constant(crit));
var leftPart = Expression.Equal(Expression.Property(parameterExpression1, "Surname"), Expression.Constant(crit));
return Expression.And(leftPart, rightPart);
}
var parameterExpression = Expression.Parameter(typeof(MyTable));
Expression basicExpression = null;
var crits = new List<string>
{
"test1",
"test2"
};
foreach (var crit in crits)
{
if (basicExpression is null)
{
basicExpression = CreateBasicExpression(parameterExpression, crit);
}
else
{
basicExpression = Expression.Or(basicExpression, CreateBasicExpression(parameterExpression, crit));
}
}
var resultExpression = Expression.Lambda(basicExpression, parameterExpression);
var castedExpression = (Expression<Func<MyTable, bool>>)resultExpression

IEnumerable.Select() when attribute is known only at runtime

Say I have a data class like this and a list of its objects:
public class DataSet
{
public int A { get; set; }
public string B { get; set; }
public double C { get; set; }
}
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
I would like to do a Select() on the list, based on different properties. For example, if I need a list of property A, I could do this easily:
var listA = data.Select(x => x.A).ToList();
All good so far.
But in my program, I need to do the above, only, I wouldn't know whether I need a list of A or B or C until runtime. This 'knowledge' of what to select is stored in a list of strings, and I need to iterate it and extract only the appropriate lists. Something like this:
// GetKeys() will return the keys that I need to extract.
// So at one time keyList could have "A" and "B", another time "B" and "C" etc.
List<string> keyList = GetKeys();
foreach (var key in keyList)
{
// What do I do here?
data.Select(x =>???).ToList();
}
Is this possible at all? I'm fine with even a non-LINQ solution, if it achieves my goal.
EDIT:
Clarifying the requirement.
The end result I want is a separate list based on each 'key' mentioned above. So, something like
List<List<object>>
The count in outer list would be the count of keyList.
The inner list would have as many items as in DataSet.
This would probably not be the most efficient solution, but you could use Reflection for a fully dynamic solution:
private static List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties)
{
// get the properties only once per call
// this isn't fast
var wantedProperties = typeof(T)
.GetProperties()
.Where(x => properties.Contains(x.Name))
.ToArray();
var result = new Dictionary<string, List<object>>();
foreach (var item in data)
{
foreach (var wantedProperty in wantedProperties)
{
if (!result.ContainsKey(wantedProperty.Name))
{
result.Add(wantedProperty.Name, new List<object>());
}
result[wantedProperty.Name].Add(wantedProperty.GetValue(item));
}
}
return result.Select(x => x.Value).ToList();
}
And, of course, you'd need to do a double foreach or a LINQ query to print that. For example:
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var selectedData = SelectDynamicData(data, new List<string> { "A", "C" });
foreach (var list in selectedData)
{
foreach (object item in list)
{
Console.Write(item + ", ");
}
Console.WriteLine();
}
Using Creating Expression Trees by Using the API you can build an expression tree to represent the linq query you were hard coding in order to make it more dynamic.
Expression<Func<TModel, object>> GetPropertyExpression<TModel>(string propertyName) {
// Manually build the expression tree for
// the lambda expression v => v.PropertyName.
// (TModel v) =>
var parameter = Expression.Parameter(typeof(TModel), "v");
// (TModel v) => v.PropertyName
var property = Expression.Property(parameter, propertyName);
// (TModel v) => (object) v.PropertyName
var cast = Expression.Convert(property, typeof(object));
var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
return expression;
}
Review the comments to understand the building of the expression tree.
This now can be used with the data to extract the desired result.
Following similar to what was provided in another answer it would be simplified to
List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties) {
return properties
.Select(_ => data.Select(GetPropertyExpression<T>(_).Compile()).ToList())
.ToList();
}
Both methods are displayed in the following example
[TestMethod]
public void TestMethod1() {
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var propertyKnownAtRuntime = "A";
var expression = GetPropertyExpression<DataSet>(propertyKnownAtRuntime);
var listA = data.Select(expression.Compile()).ToList();
//Produces
// { 1, 2, 3}
var listAC = SelectDynamicData(data, new List<string> { "A", "C" });
//Produces
//{
// { 1, 2, 3},
// { 1.1, 2.2, 3.3 }
//}
}
You can use reflection, for example
string key = "A";
var query = data.Select(x =>
{
var prop = x.GetType().GetProperty(key); //NOTE: if key does not exist this will return null
return prop.GetValue(x);
});
foreach (var value in query)
{
Console.WriteLine(value); //will print 1, 2, 3
}

Join 2 lists have different length by in LINQ

How can I join 2 lists of different lengths. it should join with the sequence.
Eg.
{1,2,3,4} with {5,6,7}
I need to get result like below.
{{1,5}, {2,6}, {3,7}, {4,null}}
I tried this.
var qry = a.Select((i, index) => new {i, j = b[index]});
But its throwing error since the lists are having different lengths.
Please help me to get the solution.
This should work:
var a = new int?[] { 1, 2, 3, 4 };
var b = new int?[] { 5, 6, 7 };
var result = Enumerable.Range(0, Math.Max(a.Count(), b.Count()))
.Select(n => new[] {a.ElementAtOrDefault(n), b.ElementAtOrDefault(n)});
Do note the ? in the array declarations. That is necessary in order to have null values in the resulting list. Omitting the ? causes the result to have 0 instead of null.
If you can't or don't want to declare the arrays as int?, then you'll have to do the cast in the Select like so:
var result = Enumerable.Range(0, Math.Max(a.Count(), b.Count()))
.Select(n => new[] { a.Select(i => (int?)i).ElementAtOrDefault(n), b.Select(i => (int?)i).ElementAtOrDefault(n) });
This second bit of code will work correctly with regular int arrays or Lists.
The ugly but working version is the following:
a.Cast<int?>().Concat(Enumerable.Repeat<int?>(null, Math.Max(b.Count() - a.Count(), 0)))
.Zip(b.Cast<int?>()
.Concat(Enumerable.Repeat<int?>(null, Math.Max(a.Count() - b.Count(), 0))),
(x, y) => new { x, y });
Its drawback it double evaluation of a collection (the first one is by calling .Count()).
So it is better just to write an extension
static IEnumerable<TResult> ZipNull<T1, T2, TResult>(this IEnumerable<T1> a, IEnumerable<T2> b, Func<T1?, T2?, TResult> func)
where T1 : struct
where T2 : struct
{
using (var it1 = a.GetEnumerator())
using (var it2 = b.GetEnumerator())
{
while (true)
{
if (it1.MoveNext())
{
if (it2.MoveNext())
{
yield return func(it1.Current, it2.Current);
}
else
{
yield return func(it1.Current, null);
}
}
else
{
if (it2.MoveNext())
{
yield return func(null, it2.Current);
}
else
{
break;
}
}
}
}
}
and use it as
a.ZipNull(b, (x, y) => new { x, y });
What you have is effectively a Zip, but where it zips to the end of the longer, rather than the shorter, of the two sequences. You can write such a Zip method, with something that looks a bit similar to the actual Zip implementation:
public static IEnumerable<TResult> ZipAll<TSource, TSecond, TResult>(this IEnumerable<TSource> source,
IEnumerable<TSecond> other,
Func<TSource, TSecond, TResult> projection)
{
using (var firstIterator = source.GetEnumerator())
using (var secondIterator = other.GetEnumerator())
{
while (true)
{
bool hasFirst = firstIterator.MoveNext();
bool hasSecond = secondIterator.MoveNext();
TSource first = hasFirst ? firstIterator.Current : default(TSource);
TSecond second = hasSecond ? secondIterator.Current : default(TSecond);
if (hasFirst || hasSecond)
yield return projection(first, second);
else
yield break;
}
}
}
With that you can write:
a.ZipAll(b, (i, j) => new { i, j });
You could make the code a bit shorter by requiring the inputs to be lists, but the code wouldn't be any faster as lists, just less typing, and it's not like it's that much extra work to support any sequence, so I'd say it's worth the added few lines of code.
Simply loop through the lists and construct new, let's say Dictionary<int?, int?> out of each list element:
var theFirstList = new List<int?> { 1, 2, 3, 4 };
var theSecondList = new List<int?> { 5, 6, 7 };
var el = new Dictionary<int?, int?>();
var length = Math.Max(theFirstList.Count, theSecondList.Count);
for (int i = 0; i < length; i++)
{
el.Add(theFirstList.ElementAtOrDefault(i), theSecondList.ElementAtOrDefault(i));
}
var x = new[] { 1, 2, 3, 4 }.ToList();
var y = new[] { 5, 6, 7 }.ToList();
var arrayLists = new[] {x, y}.OrderBy(t => t.Count).ToList();
var result = arrayLists
.Last()
.Select((item, i) => new[] { x[i], i < arrayLists.First().Count ? y[i] : (int?)null })
.ToList();
this should work for any IEnumerable

Use linq to remove elements in one list using a condition in another

I have
List<X> A = new List<X>{null,"1",null,"3"};
List<Y> B = new List<Y>{ 0 , 1 , 2 , 3 };
I want to use linq to list only the elemnts in B that have a corresponding value in A that is not null. so...
List<Y> C = [some linq expression using A and B];
C now has 1 and 3 in it.
How can this be done?
List<String> A = new List<String> { null, "1", null, "3" };
List<int> B = new List<int> { 0, 1, 2, 3 };
var C = A.Zip(B, (s, n) => new { a = s, b = n })
.Where(x => x.a != null)
.Select(x => x.b)
.ToList();
var c = B.Where((o, i) => A[i] != null).ToList();
Edit to note that it was unclear to me when this was written that both lists are aligned by index. Unsure of the value of this response given that information. It's certainly less valuable than I initially imagined.
Essentially what you want is an intersection. Here's an answer using Intersect() that works based on the data and parameters supplied in your example:
var a = new List<string> { null, "1", null, "3" };
var b = new List<int> { 0, 1, 2, 3 };
var intersection = a.Intersect(b.Select(x => x.ToString())).ToList();
You should be able to adapt to an intersection that works for you.
If both of your lists really have nullable items in them, then you'll need additional null checks on the b list (I'm just blindly calling ToString() on each item in it). But there's no reason to filter out nulls in A if B contains no nulls and you are doing an intersection, they will be filtered out as part of that process.
Consider also that:
b.Select(x => x.ToString()) ...
Could very easily be:
b.Select(x => ConvertTypeBToTypeA(x)) ...
List<string> A = new List<string> { null, "1", null, "3" };
List<int> B = new List<int> { 0, 1, 2, 3 };
var C = B.Where(x => A.Contains(x.ToString()));
How about an extension method to avoid some overhead?
public static class Ext {
public static IEnumerable<T1> WhereOther<T1, T2>(this IEnumerable<T1> src, IEnumerable<T2> filter, Func<T2, bool> pred) {
using (var isrc = src.GetEnumerator())
using (var ifilter = filter.GetEnumerator())
while (ifilter.MoveNext())
if (isrc.MoveNext())
if (pred(ifilter.Current))
yield return isrc.Current;
}
}
With that created, you can use
var ans = B.WhereOther(A, p => p != null);
You may also want an IQueryable variant, though creating one isn't that easy.
I guess you could cheat and return a lambda that applies AsEnumerable() and then uses IEnumerable.WhereOther.
try this:
var c = Enumerable.Range(0, Math.Min(B.Count, A.Count))
.Where(i => A[i] != null)
.Select(i => B[i]).ToList();

Combining lists in linq

in linq, is it possible to combine many lists (of the same type), such that two lists,
list 1 = {a,b,c} and list 2 = {x,y,z}
turns into {[1,a] , [1,b] , [1,c] , [2,x] , [2,y] , [2,z] }
where [] represents a pair containing a "list identifier"
The problem is from having decks of arbitrary cards, where each deck is a list in a collection of lists.
I'm trying to create a query such that I can select only cards in a certain deck, or cards similar to 2 or more decks.
This is probably a duplicate question, but I don't know how to search for the question further then I already have.
List<List<int>> lists;
var combined = lists.Select((l, idx) => new { List = l, Idx = idx })
.SelectMany(p => p.List.Select(i => Tuple.Create(p.Idx + 1, i)));
var list1 = new List<string>() {a,b,c};
var list2 = new List<string>() {x,y,z};
var combined = list1.Select(x => new { id = 1, v = x }).Concat(list2.Select(x => new { id = 2, v = x }));
Normally I'd suggest Enumerable.Zip for combining multiple lists, however you seem to actually want to concatenate multiple lists with a list counter.
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.Select((x,i) => x.Select(y => Tuple.Create(i+1,y))).SelectMany (l =>l);
}
UPDATE
Completely missed that SelectMany has the index option so the above code can be written as
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.SelectMany((x,i) => x.Select(y => Tuple.Create(i+1,y)));
}
Then you can do
var list1 = new List<string> { "a", "b", "c" };
var list2 = new List<string> { "x", "y", "z" };
var combined = Combine(list1,list2);
Combined will be enumerable of tuples, with Item1 being the list index identifier (starting at 1) and Item2 being the value.
This method will handle multiple lists so you could just as easily call it with:
var list3 = new List<string> { "f", "g" };
var combined = Combine(list1,list2,list3);
You can merge the lists like:
var first = new List<string> {"a","b","c"};
var second = new List<string> {"x","y","z"};
var merged = first.Select(item => new { ListIndex = 1, Value = item}).ToList();
merged.AddRange(second.Select(item => new { ListIndex = 2, Value = item});
//or use concat
var merged = first.Select(item => new { ListIndex = 1, Value = item});
.Concat(second.Select(item => new { ListIndex = 2, Value = item});
Alternatively if you have the sources in something like:
List<List<string>> lists = new List<List<string>>
{
new List<string> {"a","b","c"},
new List<string> {"x","y","z"}
};
you can do:
var merged = lists.SelectMany((item, index) =>
item.Select(s => new { ListIndex = index, Value = s}));
Note that this will produce a 0-based list, so if you really need a 1-base list, just do ListIndex = index +1.
Also, if you will use this a lot, I would create it as an specific entity, something like
struct ListIdentValue
{
public int ListIndex {get; private set;}
public string Value {get; private set;}
public ListIdentValue(int listIndex, string value) {...}
}
Try using Concat
new[] {'a','b','c'}
.Select(v=>new Tuple<int,char>(1, v))
.Concat(
new[] {'x','y','z'}.Select(v=>new Tuple<int,char>(2, v))
)
string[] a = { "a", "b", "c" };
string[] b = { "x", "z", "y" };
var t =
(
from ai in a
select new { listNo = 1, Item = ai }
).Union
(
from bi in b
select new { listNo = 2, Item = bi }
);
or
var t =
(
from ai in a
select new object[] { 1, ai }
).Union
(
from bi in b
select new object[] { 2, bi }
);

Categories

Resources