Building dynamic query in a loop using Expression trees - c#

I have a system that allows different criteria pertaining to Sales to be stored in the database. When the criteria are loaded, they are used to build a query and return all applicable Sales. The criteria objects look like this:
ReferenceColumn (The column in the Sale table they apply to)
MinValue (Minimum value the reference column must be)
MaxValue (Maximum value the reference column must be)
A search for Sales is done using a collection of the aforementioned criteria. ReferenceColumns of the same type are OR'd together, and ReferenceColumns of different types are AND'd together. So for example if I had three criteria:
ReferenceColumn: 'Price', MinValue: '10', MaxValue: '20'
ReferenceColumn: 'Price', MinValue: '80', MaxValue: '100'
ReferenceColumn: 'Age', MinValue: '2', MaxValue: '3'
The query should return all Sales where the price was between 10-20 or between 80-100, but only if those Sales's Age is between 2 and 3 years old.
I have it implemented using a SQL query string and executing using .FromSql:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
StringBuilder sb = new StringBuilder("SELECT * FROM Sale");
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
// Adding this at the start so we can always append " AND..." to each outer iteration
if (referenceFields.Count() > 0)
{
sb.Append(" WHERE 1 = 1");
}
// AND all iterations here together
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
// So we can always use " OR..."
sb.Append(" AND (1 = 0");
// OR all iterations here together
foreach (SaleCriteria sc in criteriaGrouping)
{
sb.Append($" OR {sc.ReferenceColumn} BETWEEN '{sc.MinValue}' AND '{sc.MaxValue}'");
}
sb.Append(")");
}
return _context.Sale.FromSql(sb.ToString();
}
And this is fact works just fine with our database, but it doesn't play nice with other collections, particulary the InMemory database we use for UnitTesting, so I'm trying to rewrite it using Expression trees, which I've never used before. So far I've gotten this:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
Expression masterExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
List<ParameterExpression> parameters = new List<ParameterExpression>();
// AND these...
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
Expression innerExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
ParameterExpression referenceColumn = Expression.Parameter(typeof(Decimal), criteriaGrouping.Key);
parameters.Add(referenceColumn);
// OR these...
foreach (SaleCriteria sc in criteriaGrouping)
{
Expression low = Expression.Constant(Decimal.Parse(sc.MinValue));
Expression high = Expression.Constant(Decimal.Parse(sc.MaxValue));
Expression rangeExpression = Expression.GreaterThanOrEqual(referenceColumn, low);
rangeExpression = Expression.AndAlso(rangeExpression, Expression.LessThanOrEqual(referenceColumn, high));
innerExpression = Expression.OrElse(masterExpression, rangeExpression);
}
masterExpression = Expression.AndAlso(masterExpression, innerExpression);
}
var lamda = Expression.Lambda<Func<Sale, bool>>(masterExpression, parameters);
return _context.Sale.Where(lamda.Compile());
}
It's currently throwing an ArgumentException when I call Expression.Lamda. Decimal cannot be used there and it says it wants type Sale, but I don't know what to put there for Sales, and I'm not sure I'm even on the right track here. I'm also concerned that my masterExpression is duplicating with itself each time instead of appending like I did with the string builder, but maybe that will work anyway.
I'm looking for help on how to convert this dynamic query to an Expression tree, and I'm open to an entirely different approach if I'm off base here.

I think this will work for you
public class Sale
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
//I used a similar condition structure but my guess is you simplified the code to show in example anyway
public class Condition
{
public string ColumnName { get; set; }
public ConditionType Type { get; set; }
public object[] Values { get; set; }
public enum ConditionType
{
Range
}
//This method creates the expression for the query
public static Expression<Func<T, bool>> CreateExpression<T>(IEnumerable<Condition> query)
{
var groups = query.GroupBy(c => c.ColumnName);
Expression exp = null;
//This is the parametar that will be used in you lambda function
var param = Expression.Parameter(typeof(T));
foreach (var group in groups)
{
// I start from a null expression so you don't use the silly 1 = 1 if this is a requirement for some reason you can make the 1 = 1 expression instead of null
Expression groupExp = null;
foreach (var condition in group)
{
Expression con;
//Just a simple type selector and remember switch is evil so you can do it another way
switch (condition.Type)
{
//this creates the between NOTE if data types are not the same this can throw exceptions
case ConditionType.Range:
con = Expression.AndAlso(
Expression.GreaterThanOrEqual(Expression.Property(param, condition.ColumnName), Expression.Constant(condition.Values[0])),
Expression.LessThanOrEqual(Expression.Property(param, condition.ColumnName), Expression.Constant(condition.Values[1])));
break;
default:
con = Expression.Constant(true);
break;
}
// Builds an or if you need one so you dont use the 1 = 1
groupExp = groupExp == null ? con : Expression.OrElse(groupExp, con);
}
exp = exp == null ? groupExp : Expression.AndAlso(groupExp, exp);
}
return Expression.Lambda<Func<T, bool>>(exp,param);
}
}
static void Main(string[] args)
{
//Simple test data as an IQueriable same as EF or any ORM that supports linq.
var sales = new[]
{
new Sale{ A = 1, B = 2 , C = 1 },
new Sale{ A = 4, B = 2 , C = 1 },
new Sale{ A = 8, B = 4 , C = 1 },
new Sale{ A = 16, B = 4 , C = 1 },
new Sale{ A = 32, B = 2 , C = 1 },
new Sale{ A = 64, B = 2 , C = 1 },
}.AsQueryable();
var conditions = new[]
{
new Condition { ColumnName = "A", Type = Condition.ConditionType.Range, Values= new object[]{ 0, 2 } },
new Condition { ColumnName = "A", Type = Condition.ConditionType.Range, Values= new object[]{ 5, 60 } },
new Condition { ColumnName = "B", Type = Condition.ConditionType.Range, Values= new object[]{ 1, 3 } },
new Condition { ColumnName = "C", Type = Condition.ConditionType.Range, Values= new object[]{ 0, 3 } },
};
var exp = Condition.CreateExpression<Sale>(conditions);
//Under no circumstances compile the expression if you do you start using the IEnumerable and they are not converted to SQL but done in memory
var items = sales.Where(exp).ToArray();
foreach (var sale in items)
{
Console.WriteLine($"new Sale{{ A = {sale.A}, B = {sale.B} , C = {sale.C} }}");
}
Console.ReadLine();
}

Related

How to loop through the properties of anonymous type in a list

If I have the following LINQ query:
var outstandingDataTotalData = from t1 in dtTotal.AsEnumerable()
join t2 in dtOutstandingData.AsEnumerable() on
new
{
priv_code = t1["priv_code"],
pri_ded = t1["pri_ded"].ToString().Trim()
} equals
new
{
priv_code = t2["priv_code"],
pri_ded = t2["pri_ded"].ToString().Trim()
}
into ps
from t2 in ps.DefaultIfEmpty()
select new
{
adjustment_value = t2 == null ? string.Empty : t2["adjustment_value"].ToString(),
amount_outstanding = t2 == null ? string.Empty : t2["amount_outstanding"].ToString(),
amount_outstanding_priv = t2 == null ? string.Empty : t2["amount_outstanding_priv"].ToString(),
amount_outstanding_ded = t2 == null ? string.Empty : t2["amount_outstanding_ded"].ToString(),
diff_outstanding = t2 == null ? string.Empty : t2["diff_outstanding"].ToString(),
exchange_rate = t2 == null ? string.Empty : t2["exchange_rate"].ToString(),
SalYear = t2 == null ? string.Empty : t2["sal_year"].ToString(),
SalMonth = t2 == null ? string.Empty : t2["sal_mon"].ToString()
};
Now outstandingDataTotalData is a list of anonymous type. And I have the following class:
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
}
How to loop through outstandingDataTotalData properties to fill List<AdjustmentTotal> as the following example:
If the result set of outstandingDataTotalData =
[0]{ adjustment_value = "100.00", amount_outstanding = "80.00", amount_outstanding_priv = "60.00", amount_outstanding_ded = "30.52", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
[1]{ adjustment_value = "1500.00", amount_outstanding = "5040.00", amount_outstanding_priv = "", amount_outstanding_ded = "", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
I want the result set of List<AdjustmentTotal> as:
2018 1 100.00
2018 1 1500.00
2018 1 80.00
2018 1 5040.00
2018 1 60.00
2018 1
2018 1 30.52
2018 1
2018 1 0.36
2018 1 0.36
2018 1
2018 1
Make your life easy, don't extract to separate properties. Make the extract as an array:
select new {
someArray = new[]{
t2["adjustment_value"].ToString(),
t2["amount_outstanding"].ToString(),
t2["amount_outstanding_priv"].ToString(),
t2["amount_outstanding_ded"].ToString(),
...
},
SalYear = ...,
}
So you end up with an object with 3 properties, two strings SalXxx and an array of strings (the other values). The array of strings means you can use the LINQ SelectMany it to flatten it. You'll see in the msdn example they have owners with lists of pets (variable number of pets but your values are fixed number) and after selectmany it's flattened to a list where the owner repeats and there is one pet. Your lowercases values are the pets, the SalXxx values are the owners
Once you get a working query you can actually integrate it into the first query ..
Sorry for not posting a full example (and I skipped the null checks for clarity) - the code is very hard to work with on a cellphone
Edit:
So, you say you want the results in a particular order. Both Select and SelectMany have a version where they will give the index of the item, and we can use that.. because you basically want to have these objects:
var obj = new [] {
new { SalYear = 2018, SalMonth = 1, C = new[] { "av1", "ao1", "aop1", "aod1" } },
new { SalYear = 2018, SalMonth = 2, C = new[] { "av2", "ao2", "aop2", "aod2" } }
};
Be like av1, av2, ao1, ao2.. so we want to sort the results first by the index of the inner array, then by the index of the outer array
We use a SelectMany to dig out the inner array and then for every item in the inner array we make a new object that has the data and the indexes (of the inner and the outer):
obj.SelectMany((theOuter, outerIdx) =>
theOuter.C.Select((theInner, innerIdx) =>
new {
SalYear = theOuter.SalYear,
SalMonth = theOuter.SalMonth,
DataItem = theInner,
OuterIdx = outerIdx,
InnerIdx = innerIdx
}
)
).OrderBy(newObj => newObj.InnerIdx).ThenBy(newObj => newObj.OuterIdx)
You will probably find you don't need the ThenBy; sorting by the InnerIdx will leave a tie (every InnerIdx in my list is represented twice -there are two innerIdx=0 etc) and things in linq sort as far as they can then no futher - because theyre sorted by OuterIdx already (when they went into the query) they should remain sorted by OuterIdx after they tie on InnerIdx.. If that makes sense. Belt and braces!
outstandingDataTotalData.Select(s => new AdjustmentTotal {
SalYear = s.SalYear,
SalMonth = s.SalMonth,
Value = s.adjustment_value
}).ToList();
Use a eum :
class Program
{
static void Main(string[] args)
{
List<AdjustmentTotal> totals = new List<AdjustmentTotal>();
for (int i = 0; i < (int)VALUE.END; i++)
{
foreach (var data in outstandingDataTotalData)
{
AdjustmentTotal total = new AdjustmentTotal();
totals.Add(total);
total.SalMonth = data.SalMonth;
total.SalYear = data.SalYear;
total._Type = (VALUE)i;
switch ((VALUE)i)
{
case VALUE.adjustment_value :
total.Value = data.adjustment_value;
break;
case VALUE.amount_outstanding:
total.Value = data.amount_outstanding;
break;
case VALUE.amount_outstanding_ded:
total.Value = data.mount_outstanding_ded;
break;
case VALUE.amount_outstanding_priv:
total.Value = data.amount_outstanding_priv;
break;
case VALUE.diff_outstanding:
total.Value = data.diff_outstanding;
break;
case VALUE.exchange_rate:
total.Value = data.exchange_rate;
break;
}
}
}
}
}
public enum VALUE
{
adjustment_value = 0,
amount_outstanding = 1,
amount_outstanding_priv = 2,
amount_outstanding_ded = 3,
diff_outstanding = 4,
exchange_rate = 5,
END = 6
}
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
public VALUE _Type { get; set; }
}

Order items by custom order

I have a list which I inserted on a database:
List<House> houses = new List<House> {
new House { Id = 1, Type = "A" },
new House { Id = 2, Type = "B" },
new House { Id = 3, Type = "C" }
new House { Id = 4, Type = "B" }
}
Using Linq to Entities I need to get Houses ordered by Type but it should be:
Houses of Type C
Houses of Type A
Houses of Type B
How to do this?
You can chain the ? : opeartor to create a custom sort like this:
var query = from h in context.Houses
orderby h.Type == "C" ? 0 : (h.Type == "A" ? 1 : 2)
select h;
Or method syntax
var query = context.Houses.OrderBy(h => h.Type == "C" ? 0 : (h.Type == "A" ? 1 : 2))
Sorry for late answer, but I would write an extension similar to this:
static void Main(string[] args)
{
var items = new[] { 1, 2, 3, 4, 5 }.AsQueryable();
//for example, revert entire list
var newOrder = new Dictionary<int, int>() { { 1, 5 }, { 2, 4 }, { 3, 3 }, { 4, 2 }, { 5, 1 } };
var sorted = items.OrderBy(newOrder.ToSwithExpression())).ToList();
foreach(var i in sorted)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
static Expression<Func<T, K>> ToSwithExpression<T, K>(this Dictionary<T, K> dict, K defaultValue = default(K))
{
var paramm = Expression.Parameter(typeof(T), "x");
//If nothing maps - use default value.
Expression iter = Expression.Constant(defaultValue);
foreach (var kv in dict)
{
iter = Expression.Condition(Expression.Equal(paramm, Expression.Constant(kv.Key)), Expression.Constant(kv.Value, typeof(K)), iter);
}
return Expression.Lambda<Func<T, K>>(Expression.Convert(iter, typeof(K)), paramm);
}
As you see you can specify mapping switch instead of Dictionary. I used dictionary just because it is easier. EF will have no problem in chewing this one and transforming it into similar to other answers expression.

Build a Where clause using a loop and concatening each iterarion with an OR

I have a list of N pairs of integers, e.g.:
2, 4
5, 7
9, 10
11, 12
And I need to build a query like:
WHERE
(foo = 2 AND bar = 4) OR
(foo = 5 AND bar = 7) OR
(foo = 9 AND bar = 10) OR
(foo = 11 AND bar = 12)
If it was a constant length list, I could write something like:
var query = myClass.Where(x =>
(foo == values[0][0] && bar == values[0][1]) ||
(foo == values[1][0] && bar == values[1][1]) ||
(foo == values[2][0] && bar == values[2][1]) ||
(foo == values[3][0] && bar == values[3][1]));
But the length of the list varies, and I am looking for a way to create the query using a loop.
I found I can use Queryable.Union() for a similar result, but considering there are more conditions in the query, and the list of pairs can be long, I would prefer to avoid the union.
Is there any solution for this problem?
You can perform one trick - concatenate looking for fields: foo and bar and then use Contains method:
var filters = new int[][] {
new int[] { 2, 4 },
new int[] { 5, 7 },
new int[] { 9, 10 },
new int[] { 11, 12 }
};
var newFilter = filters.Select(x => x[0] + "-" + x[1]).ToList();
var answer = dbContext.myClass.Where(x => newFilter.Contains(x.foo + "-" + x.bar)).ToList();
Assuming that values is a jagged array, and that myClass is an IEnumerable<T> of an object that has foo and bar properties:
var query = myClass.Where(x => values.Any(y => x.foo == y[0] && x.bar == y[1]));
The inner Any statement, which is run against each object in myClass, looks for any "row" in values whose contents matches the foo and bar properties of myClass. In essence, the Any clause iterates over each row in the table, while the Where clause iterates over (and filters) each object in myClass.
However, I don't know that it will be any more efficient than using a Union.
As noted in the comments, this method doesn't work with LINQ to Entities. This could still be used in conjunction with Entity Framework by pulling all of the records from the database and filtering them in memory, but obviously this is not an efficient solution.
try this:
var query = myClass.Where(x => x.Any(p => p[0] == foo && p[1] == bar));
Take your integer set and make it into a map. Then test for .Any using LINQ.
var map = new Dictionary<int, int>()
{
{2, 4},
{5, 7},
{9, 10},
{11, 12}
};
var foo = 2;
var bar = 4;
var q = map.Any(kv => foo == kv.Key && bar == kv.Value);
Alternatively, you can take the list of int pairs and make them into a list of Tuple<int, int> and test for foo and bar like this:
var q = listOfTuples.Any(tp => foo == tp.Item1 && bar == tp.Item2);
Main point here is that you need to take your "list of pairs of ints" and make a decision on how that information will be structured. Once you make that call, everything else falls into place. One step at a time, right? :-)
I found a solution for this problem based on a similar solution described at Dynamic Queries in Entity Framework using Expression Trees
Here is the solution.
First, a class to hold a foo and bar pair:
public class FooBarPair
{
public int Foo { get; set; }
public int Bar { get; set; }
}
Then, the collection of foos and bars:
var pairs = new FooBarPair[]
{
new Foo() { Foo = 10537, Bar = 1034 },
new Foo() { Foo = 999, Bar = 999 },
new Foo() { Foo = 888, Bar = 888 },
new Foo() { Foo = 10586, Bar = 63 },
};
And here is the code that builds the query expression:
public static void Main()
{
Expression<Func<MyClass, bool>> whereClause =
BuildOrExpressionTree<MyClass, int>(pairs, m => m.Foo + m.Bar);
var myClasss = model.Set<MyClass>();
IQueryable<MyClass> query = myClasss.Where(whereClause);
}
/// <summary>
/// Starts a recursion to build WHERE (m.Foo = X1 AND m.Bar = Y1) [OR (m.Foo = X2 AND m.Bar = Y2) [...]].
/// </summary>
private static Expression<Func<TValue, bool>> BuildOrExpressionTree<TValue, TCompareAgainst>(
IEnumerable<FooBarPair> wantedItems,
Expression<Func<TValue, TCompareAgainst>> convertBetweenTypes1)
{
ParameterExpression inputParam1 = convertBetweenTypes1.Parameters[0];
BinaryExpression binaryExpression = convertBetweenTypes1.Body as BinaryExpression;
Expression binaryExpressionTree = BuildBinaryOrTree<FooBarPair>(
wantedItems.GetEnumerator(),
binaryExpression.Left,
binaryExpression.Right,
null);
return Expression.Lambda<Func<TValue, bool>>(binaryExpressionTree, new[] { inputParam1 });
}
/// <summary>
/// Recursive function to append one 'OR (m.Foo = X AND m.Bar = Y)' expression.
/// </summary>
private static Expression BuildBinaryOrTree<T>(
IEnumerator<FooBarPair> itemEnumerator,
Expression expressionToCompareTo1,
Expression expressionToCompareTo2,
Expression prevExpression)
{
if (itemEnumerator.MoveNext() == false)
{
return prevExpression;
}
ConstantExpression fooConstant = Expression.Constant(itemEnumerator.Current.Foo, typeof(int));
ConstantExpression barConstant = Expression.Constant(itemEnumerator.Current.Bar, typeof(int));
BinaryExpression fooComparison = Expression.Equal(expressionToCompareTo1, fooConstant);
BinaryExpression barComparison = Expression.Equal(expressionToCompareTo2, barConstant);
BinaryExpression newExpression = Expression.AndAlso(fooComparison, barComparison);
if (prevExpression != null)
{
newExpression = Expression.OrElse(prevExpression, newExpression);
}
return BuildBinaryOrTree<FooBarPair>(
itemEnumerator,
expressionToCompareTo1,
expressionToCompareTo2,
newExpression);
}
Thanks everybody!

Group by same value and contiguous date

var myDic = new SortedDictionary<DateTime,int> ()
{ { new DateTime(0), 0 },
{ new DateTime(1), 1 },
{ new DateTime(2), 1 },
{ new DateTime(3), 0 },
{ new DateTime(4), 0 },
{ new DateTime(5), 2 }
};
How can group these items (with a LINQ request) like this :
group 1 :
startDate: 0, endDate:0, value:0
group 2 :
startDate: 1, endDate:2, value:1
group 3 :
startDate: 3, endDate:4, value:0
group 4 :
startDate: 5, endDate:5, value:2
group are defined by contiguous date and same values.
Is it possible with a groupby ?
Just use a keyGenerating function. This example presumes your dates are already ordered in the source with no gaps.
int currentValue = 0;
int groupCounter = 0;
Func<KeyValuePair<DateTime, int>, int> keyGenerator = kvp =>
{
if (kvp.Value != currentValue)
{
groupCounter += 1;
currentValue = kvp.Value;
}
return groupCounter;
}
List<IGrouping<int, KeyValuePair<DateTime, int>> groups =
myDictionary.GroupBy(keyGenerator).ToList();
It looks like you are trying to group sequential dates over changes in the value. I don't think you should use linq for the grouping. Instead you should use linq to order the dates and iterate over that sorted list to create your groups.
Addition 1
While you may be able to build your collections with by using .Aggregate(). I still think that is the wrong approach.
Does your data have to enter this function as a SortedDictionary?
I'm just guessing, but these are probably records ordered chronologically.
If so, do this:
public class Record
{
public DateTime Date { get; set; }
public int Value { get; set; }
}
public class Grouper
{
public IEnumerable<IEnumerable<Record>> GroupRecords(IEnumerable<Record> sortedRecords)
{
var groupedRecords = new List<List<Record>>();
var recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
foreach (var record in sortedRecords)
{
if (recordGroup.Count > 0 && recordGroup.First().Value != record.Value)
{
recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
}
recordGroup.Add(record);
}
return groupedRecords;
}
}

Group by in linq + select

say I have this data
1 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 3
2 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 3 1
3 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 2
column 1 // pk
column 2 // userId
column 3 // courseId
column 4 // permissionId
I have this class
class CoursePermissions
{
public string Prefix { get; set; }
public bool OwnerPermission { get; set; } // permissionId 1
public bool AddPermission { get; set; } // permissionId 2
public bool EditPermission { get; set; } // permissionId 3
}
I want to group all the 3 rows by courseId(or Prefix) and then take that information and make a class out Of it
So the end result would be
List<CoursePermissions> permissions = new List<CoursePermissions>();
CoursePermissions a = new CoursePermissions
{
Prefix = "comp101";
OwnerPermission = false,
AddPermission = true,
EditPermission = true
};
CoursePermissions b = new CoursePermissions
{
Prefix = "comp102";
OwnerPermission = true,
AddPermission = false,
EditPermission = false
};
permissions.Add(a);
permissions.Add(b);
So the above is how the object would look if I took all the row data from the db and manually made it the way I wanted it too look. Of course I need to do it somehow as a query.
In my example I have 2 students. They both belong to the same course. Student 1has edit and Add permission for Comp101 but only owner permissions for comp102.
I want to get all the rows back for Comp101 and put it into CoursePermissions. Then I want to get all the rows back for Comp102 and put it into CoursePermissions. Then store all these in a collection and use them.
The only thing I can do is something like this
var list = session.Query<PermissionLevel>().Where(u => u.Student.StudentId == studentId).ToList();
IEnumerable<IGrouping<string, PermissionLevel>> test = list.GroupBy(x => x.Course.Prefix);
foreach (var t in test)
{
CoursePermissions c = new CoursePermissions();
foreach (var permissionLevel in t)
{
if (permissionLevel.PermissionLevelId == 1)
{
c.OwnerPermission = true;
}
}
}
It would nice if I could get rid of the nest for each loop and do it all when the data comes from the query.
Here's an approach that I think is quite functional.
First set up a dictionary of actions that will set the appropriate course permission given a permission level id.
var setPermission = new Dictionary<int, Action<CoursePermissions>>()
{
{ 1, cps => cps.OwnerPermission = true },
{ 2, cps => cps.AddPermission = true },
{ 3, cps => cps.EditPermission = true },
};
Now create a function that will turn the course prefix and a list of permission level ids into a new CoursePermissions object.
Func<string, IEnumerable<int>, CoursePermissions>
buildCoursePermission = (prefix, permissionLevelIds) =>
{
var cps = new CoursePermissions() { Prefix = prefix };
foreach (var permissionLevelId in permissionLevelIds)
{
setPermission[permissionLevelId](cps);
}
return cps;
};
Now all you have left is a simple query that turns your list of permission levels into a list of course permissions.
var coursePermissionsList =
(from pl in list
group pl.PermissionLevelId by pl.Course.Prefix into gcpls
select buildCoursePermission(gcpls.Key, gcpls)).ToList();
How does that work for you?

Categories