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
Related
I have a lambda expression like
x => x.Property0 == "Z" && old.Any(y => y.Key0 == x.Key0 && y.Property0 != x.Property0)
This expression is passed into the method as a string because it comes from a configuration file. That means I have to convert the string into an expression in order to then execute it.
public override async Task<IList<T>> CalculateList<T>(IList<T> old, IList<T> current)
{
string filter = "x => x.Property0 == \"Z\" && old.Any(y => y.Key0 == x.Key0 && y.Property0 != x.Property0)";
var exp = DynamicExpressionParser.ParseLambda<T, bool>(ParsingConfig.Default, false, filter, new object[0]);
var func = exp.Compile();
return current.Where(func).ToList();
}
If I only enter "x => x.Property0 == \" Z \"" in the filter variable, then the result fits, so the problem seems to be the old.Any, but I have not yet found a solution to the problem . However, no error is thrown, so no indication of the problem.
Can anyone tell me why the expression is not working correctly, or what I need to adjust to make it work.
Thanks
old is a variable, you should pass a value to it.
string filter = "x => x.Property0 == \"AA\" && #0.Any(y => y.Key0 == x.Key0 && y.Property0 != x.Property0 )";
var exp = DynamicExpressionParser.ParseLambda<T, bool>(ParsingConfig.Default, false, filter, old);
var func = exp.Compile();
return current.Where(func).ToList();
Example:
public async Task<IActionResult> IndexAsync()
{
IList<Employee> current = new List<Employee>
{
new Employee{ Id = 1, Name = "AA"},
new Employee{ Id = 2, Name = "BB"},
new Employee{ Id = 3, Name = "CC"},
new Employee{ Id = 4, Name = "DD"},
};
IList<Employee> old = new List<Employee>
{
new Employee{ Id = 1, Name = "BB"},
new Employee{ Id = 2, Name = "AA"},
new Employee{ Id = 4, Name = "DD"},
};
var result = CalculateList(old, current);
return View();
}
public IList<T> CalculateList<T>(IList<T> old, IList<T> current)
{
string filter = "x => x.Name == \"AA\" && #0.Any(y => y.Id == x.Id && y.Name != x.Name)";
var exp = DynamicExpressionParser.ParseLambda<T, bool>(ParsingConfig.Default, false, filter, old);
var func = exp.Compile();
return current.Where(func).ToList();
}
Result:
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
}
I have LINQ query that I want to generate dynamically:
var groupData =
from l in data
group l by l.Field1 into field1Group
select new MenuItem()
{
Key = field1Group.Key,
Count = field1Group.Count(),
Items = (from k in field1Group
group k by k.Field2 into field2Group
select new MenuItem()
{
Key = field2Group.Key,
Count = field2Group.Count()
}).ToList()
};
The ultimate goal is to be able to dynamically group the data by any combination of fields with no limit on the nested queries.
I can get as far as the first level but I'm struggling with the nested sub queries:
string field1 = "Field1";
string field2 = "Field2";
var groupDataD =
data.
GroupBy(field1, "it").
Select("new ( it.Key, it.Count() as Count )");
Is this possible with chained dynamic LINQ? Or is there a better way to achieve this?
The following should work (though personally I would rather avoid using such code):
Follow this answer to add the following in ParseAggregate, :
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
// Change starts here
var originalIt = it;
var originalOuterIt = outerIt;
// Change ends here
outerIt = it;
ParameterExpression innerIt = Expression.Parameter(elementType, elementType.Name);
it = innerIt;
Expression[] args = ParseArgumentList();
// Change starts here
it = originalIt;
outerIt = originalOuterIt;
// Change ends here
...
}
Add Select, GroupBy, ToList into IEnumerableSignatures, and respective conditions in ParseAggregate, as explained in this answer:
interface IEnumerableSignatures
{
...
void GroupBy(object selector);
void Select(object selector);
void ToList();
...
}
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
...
if (signature.Name == "Min" ||
signature.Name == "Max" ||
signature.Name == "GroupBy" ||
signature.Name == "Select")
...
}
Finally, Your query would be:
string field1 = "Field1";
string field2 = "Field2";
var result =
data
.GroupBy(field1, "it")
.Select($#"new (
it.Key,
it.Count() as Count,
it.GroupBy({field2})
.Select(new (it.Key, it.Count() as Count))
.ToList() as Items
)");
Note that "it" holds a different instance when used in the parent query vs. the subquery. I tried to take advantage of "outerIt" to overcome this conflation, but unfortunately without success (but maybe you'd succeed? maybe 1, 2 would help)
A simple example for future reference:
public class Person
{
public string State { get; set; }
public int Age { get; set; }
}
public static Main()
{
var persons = new List<Person>
{
new Person { State = "CA", Age = 20 },
new Person { State = "CA", Age = 20 },
new Person { State = "CA", Age = 30 },
new Person { State = "WA", Age = 60 },
new Person { State = "WA", Age = 70 },
};
var result = persons
.GroupBy("State", "it")
.Select(#"new (
it.Key,
it.Count() as Count,
it.GroupBy(Age)
.Select(new (it.Key, it.Count() as Count))
.ToList() as Items
)");
foreach (dynamic group in result)
{
Console.WriteLine($"Group.Key: {group.Key}");
foreach (dynamic subGroup in group.Items)
{
Console.WriteLine($"SubGroup.Key: {subGroup.Key}");
Console.WriteLine($"SubGroup.Count: {subGroup.Count}");
}
}
}
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));
Im tryig to transform a SP, into a linq query
This is my SP right now :
UPDATE PatientAlertsSummary
SET IsViewed =1
WHERE PatientID IN (SELECT PatientID FROM PatientTherapist WHERE TherapistID=#TherapistID)
I tried to make a query linq code and this is (so far) my linq query:
I came across some difficulty with doing this
var query = from pas in context.PatientAlertsSummaries
where pas.PatientID.Contains(from pt in context.PatientTherapists where pt.TherapistID == therapistid )
select pas;
foreach (var item in query)
{
item.IsViewed = true;
}
how can I make it right ?
You could change the query to join on the tables to retrieve the results you want, then update the records accordingly:
var query = from pas in context.PatientAlertsSummaries
join pt in context.PatientTherapists on pas.PatientID equals pt.PatientID
where pt.TherapistID == therapistid
select pas;
foreach (var item in query)
{
item.IsViewed = true;
}
Obviously this is untested and based on your current sql / linq query.
You can do something like this:
public class PatientAlertsSummary
{
public bool IsViewed;
public int PatientID;
}
public class PatientTherapist
{
public int PatientID;
public int TherapistID;
}
public void UpdateIsViewed(PatientAlertsSummary summary)
{
summary.IsViewed = true;
}
[TestMethod]
public void Test1()
{
//UPDATE PatientAlertsSummary
//SET IsViewed =1
//WHERE PatientID IN (SELECT PatientID FROM PatientTherapist WHERE TherapistID=#TherapistID)
var therapistId = 1;
var patientAlertsSummaries = new List<PatientAlertsSummary>() {
new PatientAlertsSummary(){PatientID = 1},
new PatientAlertsSummary(){PatientID = 2},
new PatientAlertsSummary(){PatientID = 3}
};
var PatientTherapists = new List<PatientTherapist>() {
new PatientTherapist(){PatientID = 1 , TherapistID = 1},
new PatientTherapist(){PatientID = 2 , TherapistID = 1},
new PatientTherapist(){PatientID = 3, TherapistID = 2}
};
var updatedPatinets1 = PatientTherapists.
Where(o => o.TherapistID == therapistId).
Join(patientAlertsSummaries,
patientTherapist => patientTherapist.PatientID,
patientAlertsSummary => patientAlertsSummary.PatientID,
(o, p) =>
{
UpdateIsViewed(p);
return p;
}).ToList();
}