Prevent joining table if no field selected in GraphQL - c#

I'm new to GraphQL.
Currently, I have a query definition which searches for students and their participated classes:
var studentsQueryArguments = new QueryArguments();
studentsQueryArguments.Add(new QueryArgument<ListGraphType<IntGraphType>> { Name = "ids", Description = "Student indexes." });
studentsQueryArguments.Add(new QueryArgument<RangeModelType<double?, double?>> {Name = "age", Description = "Age range of student."});
Field<ListGraphType<StudentType>>(
"students",
arguments: studentsQueryArguments,
resolve: context =>
{
var students = relationalDbContext.Students.AsQueryable();
var classes = relationalDbContext.Classes.AsQueryable();
var participatedClasses = relationalDbContext.StudentInClasses.AsQueryable();
var ids = context.GetArgument<List<int>>("ids");
var age = context.GetArgument<RangeModel<double?, double?>>("age");
if (ids != null)
students = students.Where(x => ids.Contains(x.Id));
if (age != null)
{
var from = age.From;
var to = age.To;
if (from != null)
students = students.Where(x => x.Age >= from);
if (to != null)
students = students.Where(x => x.Age <= to);
}
var results = (from student in students
select new StudentViewModel
{
Id = student.Id,
Age = student.Age,
FullName = student.FullName,
Photo = student.Photo,
Classes = from participatedClass in participatedClasses
from oClass in classes
where participatedClass.StudentId == student.Id &&
participatedClass.ClassId == oClass.Id
select new ClassViewModel
{
Id = oClass.Id,
ClosingHour = oClass.ClosingHour,
Name = oClass.Name,
OpeningHour = oClass.OpeningHour
}
});
return results;
});
In my code above, I join Student and Class.
With the query
{
students(ids: [1, 2, 3]) {
id
age
classes {
name
openingHour
closingHour
}
}
}
Students and their classes are returned. That is ok.
What I want is when I use this query:
{
students(ids: [1, 2, 3]) {
id
age
}
}
My app will not join Student with Class, and just return Student information only.
Is it possible ?
Thanks,

I made a super cool extension method that returns whether the selection is included in a GraphQL query.
Include the navigation property to your query if it exists in the selections.
Extension Method
using System;
using System.Linq;
using GraphQL.Language.AST;
using GraphQL.Types;
public static class ContextExtensions
{
/// <summary>
/// Returns true if the given fieldSelector exists in the selection of the query.
/// </summary>
/// <param name="context">The working context</param>
/// <param name="fieldSelector">The query of the field selector. For example items:organizationUnits:displayName</param>
/// <param name="namespaceSeperator">The seperator character of the fieldSelector. Default is :</param>
/// <returns></returns>
public static bool HasSelectionField(this ResolveFieldContext<object> context, string fieldSelector, char namespaceSeperator = ':')
{
if (string.IsNullOrWhiteSpace(fieldSelector))
{
return false;
}
if (context.SubFields == null)
{
return false;
}
var fragments = fieldSelector.Split(new[] { namespaceSeperator }, StringSplitOptions.RemoveEmptyEntries);
if (fragments.Length == 1)
{
return context.SubFields.ContainsKey(fragments[0]);
}
if (context.SubFields[fragments[0]] == null)
{
return false;
}
if (context.SubFields[fragments[0]].SelectionSet == null)
{
return false;
}
if (context.SubFields[fragments[0]].SelectionSet.Selections == null)
{
return false;
}
var selections = context.SubFields[fragments[0]].SelectionSet.Selections;
for (var i = 1; i < fragments.Length; i++)
{
if (selections == null)
{
return false;
}
var field = selections.Select(selection => (Field)selection).FirstOrDefault(f => f.Name == fragments[i]);
if (field == null)
{
return false;
}
if (i == fragments.Length - 1)
{
return true;
}
selections = field.SelectionSet?.Selections;
}
return true;
}
}
Usage
protected override async Task<PagedResultDto<UserDto>> Resolve(ResolveFieldContext<object> context)
{
var total_count_exists = context.HasSelectionField("totalCount"); //true
var items_name_exists = context.HasSelectionField("items:name"); //true
var items_roles_name_exists = context.HasSelectionField("items:roles:name"); //true
var items_organizationUnits_displayName_exists = context.HasSelectionField("items:organizationUnits:displayName"); //true
var items_organizationUnits_xyz_exists = context.HasSelectionField("items:organizationUnits:xyz"); //false
}
Sample Query
query MyQuery {
users(id: 1) {
totalCount
items {
name
surname
roles {
id
name
displayName
}
organizationUnits {
id
code
displayName
}
}
}
}

Related

Cross context communication EF Core

I have Student and Backpack entities:
internal class Student
{
public Guid Guid { get; set; }
public string Name { get; set; }
ICollection<Backpack> backpacks;
public virtual ICollection<Backpack> Backpacks
{
get
{
if (backpacks == null && LazyLoader != null)
{
backpacks = LazyLoader.Load(this, ref backpacks);
}
return backpacks;
}
set
{
backpacks = value;
}
}
ILazyLoader LazyLoader;
public Student(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
public Student()
{
}
}
internal class Backpack
{
public Backpack()
{
}
ILazyLoader LazyLoader;
public Backpack(ILazyLoader lazyLoader)
{
LazyLoader = lazyLoader;
}
public Guid Guid { get; set; }
public string Name { get; set; }
Student student;
public virtual Student Student
{
get
{
if (student == null && LazyLoader != null)
{
student = LazyLoader.Load(this, ref student);
}
return student;
}
set
{
student = value;
}
}
}
When an entity is changed and saved from one context and is used in another context, I want to update them. For example:
Scenario 1: when changing primitive properties and saved, I am updating the entry using reload:
var context1 = new MainContext();
var studentDilshodFromContext1 = context1.Set<Student>().First(s => s.Name == "Mike");
var context2 = new MainContext();
var studentMikeFromContext2 = context2.Set<Student>().First(s => s.Name == "Mike");
studentMikeFromContext1.Name = "Jake";
context1.SaveChanges();
context2.Entry(studentMikeFromContext2).Reload();
Console.WriteLine(studentMikeFromContext2.Name); //Nice! it is Jake after reloading
Scenario 2: now, I want to change the navigation property:
var context1 = new MainContext();
var jakeStudent = context1.Set<Student>().First(s => s.Name == "Jake");
var mikeBackpackFromContext1 = context1.Set<Backpack>().First(s => s.Name == "Mike's Backpack");
mikeBackpackFromContext1.Student = jakeStudent;
var context2 = new MainContext();
var mikeBackpackFromContext2 = context2.Set<Backpack>().First(s => s.Name == "Mike's Backpack");
context1.SaveChanges();
context2.Entry(mikeBackpackFromContext2).Reload();
Console.WriteLine(mikeBackpackFromContext2.Student.Name); //Object reference exception because Student is null. I am expecting Jake
Scenario 3: when the item was added to the navigation collection property, I would like to see it in context2:
var context1 = new MainContext();
var jakeStudentFromContext1 = context1.Set<Student>().First(s => s.Name == "Jake");
var newBackpack = new Backpack() { Student = jakeStudentFromContext1, Guid = Guid.NewGuid(), Name = "New Jake backpack" };
context1.Add(newBackpack);
var context2 = new MainContext();
var jakeStudentFromContext2 = context2.Set<Student>().First(s => s.Name == "Jake");
var backpacks = jakeStudentFromContext2.Backpacks;
context1.SaveChanges();
context2.Entry(jakeStudentFromContext2).Reload();
Console.WriteLine(jakeStudentFromContext2.Backpacks.Any(d => d.Guid == newBackpack.Guid)); //It is false but I am expecting true
As you can see entry.Reload() working fine only with primitive type properties, but not working with navigation properties. I tried NavigationEntry.Load and CollectionEntry.Load but they are not working as well.
So, in these scenarios how can I re-load my navigation properties?

Iterate through nested collection and form org structure with Parent

I have a batch of users defined with that model.
I need to iterate inside Manager nesting while manager is not null and create Departments entities with parents relation. I have an example of code what I already have, so I post it here.
What I already have:
public class AdUserModel
{
public string UserName { get; set; }
public AdUserModel Manager { get; set; }
}
...
List<UserDepartment> userDepartmentsToAdd = new List<UserDepartment>();
List<Department> newDeps = new List<Department>();
RecurseDepartments(adUser);
var userDepartment = new UserDepartment
{
User = user,
PositionName = adUser.PositionName,
Department = newDeps.FirstOrDefault(x => x.Name == adUser.DepartmentName),
IsHeadUser = user.SubordinateUsers?.Any() ?? false
};
userDepartmentsToAdd.Add(userDepartment);
void RecurseDepartments(AdUserModel model)
{
var department = new Department();
var existDep = newDeps.FirstOrDefault(x => x.Name ==
model.DepartmentName);
if (existDep == null)
{
department.Name = model.DepartmentName;
}
if (model.Manager is not null)
{
if (newDeps.FirstOrDefault(x => x.Name == model.Manager.DepartmentName) == null)
{
var parentDepartment = new Department
{
Name = model.Manager.DepartmentName
};
department.ParentDepartment = existDep ?? department;
if (existDep == null)
{
newDeps.Add(department);
}
newDeps.Add(parentDepartment);
}
if (model.Manager.DepartmentName != model.DepartmentName)
{
RecurseDepartments(model.Manager);
}
}
}
Thanks for any help in advance, being stuck here for some reason.

Combine Lambda Expressions using custom operator

I'm implementing a search page where the user can filter the result using a bunch of different filters. I need to pass a Lambda Expression to the search layer in order for the filtering to take place.
My problem is that I don't know how to build a Lambda expression dynamically.
I've tried combining Expressions using AndAlso() but that doesn't work since my Lambda expressions doesn't return a bool.
So I'm guessing I need to implement a ExpressionVisitor and that's where I'm lost right now.
// The custom operator for AFilterType
public static AFilterType operator &(AFilterType first, AFilterType second)
{
return new AndFilter(AFilterType.GetFilters<AndFilter>(first, second));
}
// Here's a simplified version of what I'm trying to do
var filterInput = new FilterInput() { FirstName = "John", LastName = "Doe" };
// Using Match() which is a AFilterType method
Expression<Func<Person, AFilterType>> firstNameFilterExpression = x => x.firstName.Match(filterInput.FirstName);
Expression<Func<Person, AFilterType>> lastNameFilterExpression = x => x.LastName.Match(filterInput.LastName);
// How can I combine those 2 expressions into 1 single Expression at runtime using the custom operator '&' (not the bool '&&').
// Combined Expression should be like this.
Expression<Func<Person, AFilterType>> combinedFilterExpression = x => x.firstName.Match(filterInput.FirstName) & x.LastName.Match(filterInput.LastName);
I've had the same problem once and I solved it using LinqKit and a little bit of reflection (I've used this in EntityFramework project, but it can be adapted to other types, if desired). I will try to post my stripped code below (hope its not too long).
Predisposition: Inclusion of LinqKit (https://www.nuget.org/packages/LinqKit or via NuGet) version 1.1.7.2 or higher.
The code consists of several files in subdirectories:
Interfaces
Framework
Extensions
Interfaces\IPredicateParser.cs
using System;
using System.Collections.Generic;
namespace LambdaSample.Interfaces
{
// Used to defined IPredicateParser for parsing predicates
public interface IPredicateParser
{
bool Parse(string text, bool rangesAllowed, Type definedType);
List<IPredicateItem> Items { get; }
}
}
Interfaces\IPredicateItem.cs
namespace LambdaSample.Interfaces
{
public interface IPredicateItem
{
bool IsValid { get; }
}
}
Framework\PredicateItemSingle.cs
using LambdaSample.Interfaces;
namespace LambdaSample.Framework
{
/// <summary>
/// Item for single predicate (e.g. "44")
/// </summary>
public class PredicateItemSingle : IPredicateItem
{
public PredicateItemSingle()
{
}
public bool IsValid => Value != null;
public object Value { get; set; }
}
}
Framework\PredicateItemRange.cs
using LambdaSample.Interfaces;
namespace LambdaSample.Framework
{
/// <summary>
/// Item for range predicates (e.g. "1-5")
/// </summary>
public class PredicateItemRange : IPredicateItem
{
public PredicateItemRange()
{
}
public bool IsValid => Value1 != null && Value2 != null;
public object Value1 { get; set; }
public object Value2 { get; set; }
}
}
Framework\PredicateParser.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using LambdaSample.Extensions;
using LambdaSample.Interfaces;
namespace LambdaSample.Framework
{
/// <summary>
/// Simple parser for text used in search fields for
/// searching through records or any values
/// </summary>
public class PredicateParser : IPredicateParser
{
private enum RangeType
{
None,
From,
To
}
public PredicateParser()
{
Items = new List<IPredicateItem>();
}
public bool Parse(string text, bool rangesAllowed, Type definedType)
{
Items.Clear();
if (string.IsNullOrWhiteSpace(text))
return true;
var result = true;
var items = text.Split(',');
foreach (var item in items)
{
object val1, val2;
bool isRange;
var ranges = item.Split('-');
if (rangesAllowed && ranges.Length == 2) // Range is only when ranges are allowed and length is 2, otherwise its single value.
{
object val1Temp, val2Temp;
if (ParseValue(ranges[0], definedType, RangeType.From, out isRange, out val1, out val1Temp) &&
ParseValue(ranges[1], definedType, RangeType.To, out isRange, out val2, out val2Temp))
{
Items.Add(new PredicateItemRange { Value1 = val1, Value2 = val2, });
}
else
{
result = false;
}
}
else
{
if (ParseValue(item, definedType, RangeType.None, out isRange, out val1, out val2))
{
if (isRange)
{
Items.Add(new PredicateItemRange { Value1 = val1, Value2 = val2, });
}
else
{
Items.Add(new PredicateItemSingle { Value = val1, });
}
}
else
{
result = false;
}
}
}
return result;
}
private bool ParseValue(string value, Type definedType, RangeType rangeType, out bool isRange, out object result, out object result2)
{
result = null;
result2 = null;
isRange = false;
if (string.IsNullOrWhiteSpace(value))
return false;
// Enums are also treated like ints!
if (definedType == typeof(int) || definedType.IsEnum)
{
int val;
if (!int.TryParse(value, out val))
return false;
result = val;
return true;
}
if (definedType == typeof(long))
{
long val;
if (!long.TryParse(value, out val))
return false;
result = val;
return true;
}
if (definedType == typeof(decimal))
{
decimal val;
if (!decimal.TryParse(value, NumberStyles.Number ^ NumberStyles.AllowThousands, new CultureInfo("sl-SI"), out val))
return false;
result = val;
return true;
}
if (definedType == typeof(DateTime))
{
int year, month, yearMonth;
if (value.Length == 4 && int.TryParse(value, out year) && year >= 1000 && year <= 9999) // If only year, we set whole year's range (e.g. 2015 ==> 2015-01-01 00:00:00.0000000 - 2015-12-31 23:59:59.9999999
{
// Default datetime for From range and if no range
result = new DateTime(year, 1, 1);
switch (rangeType)
{
case RangeType.None:
result2 = ((DateTime)result).AddYears(1).AddMilliseconds(-1);
isRange = true;
break;
case RangeType.To:
result = ((DateTime)result).AddYears(1).AddMilliseconds(-1);
break;
}
return true;
}
if (value.Length == 6 && int.TryParse(value, out yearMonth) && yearMonth >= 100001 && yearMonth <= 999912) // If only year and month, we set whole year's range (e.g. 201502 ==> 2015-02-01 00:00:00.0000000 - 2015-02-28 23:59:59.9999999
{
year = Convert.ToInt32(yearMonth.ToString().Substring(0, 4));
month = Convert.ToInt32(yearMonth.ToString().Substring(4, 2));
// Default datetime for From range and if no range
result = new DateTime(year, month, 1);
switch (rangeType)
{
case RangeType.None:
result2 = ((DateTime)result).AddMonths(1).AddMilliseconds(-1);
isRange = true;
break;
case RangeType.To:
result = ((DateTime)result).AddMonths(1).AddMilliseconds(-1);
break;
}
return true;
}
DateTime val;
if (!value.ParseDateTimeEx(CultureInfo.InvariantCulture, out val))
{
return false;
}
if (val.Hour == 0 && val.Minute == 0)
{
// No hours and minutes specified, searching whole day or to the end of the day.
// If this is no range, we make it a range
result = new DateTime(val.Year, val.Month, val.Day);
switch (rangeType)
{
case RangeType.None:
result2 = ((DateTime)result).AddDays(1).AddMilliseconds(-1);
isRange = true;
break;
case RangeType.To:
result = ((DateTime)result).AddDays(1).AddMilliseconds(-1);
break;
}
return true;
}
result = val;
return true;
}
if (definedType == typeof(string))
{
result = value;
return true;
}
return false;
}
public List<IPredicateItem> Items { get; private set; }
}
}
Extensions\StringExtensions.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace LambdaSample.Extensions
{
public static class StringExtensions
{
private static List<string> GetValidDateTimeFormats()
{
var dateFormats = new[]
{
"dd.MM.yyyy",
"yyyy-MM-dd",
"yyyyMMdd",
}.ToList();
var timeFormats = new[]
{
"HH:mm:ss.fff",
"HH:mm:ss",
"HH:mm",
}.ToList();
var result = (from dateFormat in dateFormats
from timeFormat in timeFormats
select $"{dateFormat} {timeFormat}").ToList();
return result;
}
public static bool ParseDateTimeEx(this string #this, CultureInfo culture, out DateTime dateTime)
{
if (culture == null)
{
culture = CultureInfo.InvariantCulture;
}
if (DateTime.TryParse(#this, culture, DateTimeStyles.None, out dateTime))
return true;
var dateTimeFormats = GetValidDateTimeFormats();
if (DateTime.TryParseExact(#this, dateTimeFormats.ToArray(), culture, DateTimeStyles.None, out dateTime))
return true;
return false;
}
}
}
Extensions\ObjectExtensions.cs
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace LambdaSample.Extensions
{
public static class ObjectExtensions
{
/// <summary>
/// Build Filter Dictionary<string,string> used in ExpressionExtensions.BuildPredicate to build
/// predicates for Predicate Builder based on class's properties values. Filters are then used
/// by PredicateParser, which converts them to appropriate types (DateTime, int, decimal, etc.)
/// </summary>
/// <param name="this">Object to build dictionary from</param>
/// <param name="includeNullValues">Includes null values in dictionary</param>
/// <returns>Dictionary with string keys and string values</returns>
public static Dictionary<string, string> ToFilterDictionary(this object #this, bool includeNullValues)
{
var result = new Dictionary<string, string>();
if (#this == null || !#this.GetType().IsClass)
return result;
// First, generate Dictionary<string, string> from #this by using reflection
var props = #this.GetType().GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(#this);
if (value == null && !includeNullValues)
continue;
// If value already is a dictionary add items from this dictionary
var dictValue = value as IDictionary;
if (dictValue != null)
{
foreach (var key in dictValue.Keys)
{
var valueTemp = dictValue[key];
if (valueTemp == null && !includeNullValues)
continue;
result.Add(key.ToString(), valueTemp != null ? valueTemp.ToString() : null);
}
continue;
}
// If property ends with list, check if list of generics
if (prop.Name.EndsWith("List", false, CultureInfo.InvariantCulture))
{
var propName = prop.Name.Remove(prop.Name.Length - 4, 4);
var sb = new StringBuilder();
var list = value as IEnumerable;
if (list != null)
{
foreach (var item in list)
{
if (item == null)
continue;
if (sb.Length > 0)
sb.Append(",");
sb.Append(item.ToString());
}
result.Add(propName, sb.ToString());
}
continue;
}
var str = value != null ? value.ToString() : null;
result.Add(prop.Name, str);
}
return result;
}
}
}
Extensions\ExpressionExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using LambdaSample.Framework;
using LambdaSample.Interfaces;
using LinqKit;
namespace LambdaSample.Extensions
{
public static class ExpressionExtensions
{
private static readonly MethodInfo StringContainsMethod = typeof(string).GetMethod(#"Contains", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
private static readonly MethodInfo StringStartsWithMethod = typeof(string).GetMethod(#"StartsWith", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
private static readonly MethodInfo StringEndsWithMethod = typeof(string).GetMethod(#"EndsWith", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null);
private static readonly MethodInfo ObjectEquals = typeof(object).GetMethod(#"Equals", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object) }, null);
//private static readonly MethodInfo BooleanEqualsMethod = typeof(bool).GetMethod(#"Equals", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(bool) }, null);
/// <summary>
/// Build a predicate with linq clauses, taking searchCriteria object's properties to define where conditions.
/// </summary>
/// <typeparam name="TDbType">Type of entity to build predicate for</typeparam>
/// <param name="searchCriteria">Object which contains criteria for predicate</param>
/// <param name="predicateParser">Implementation of predicate parser that will parse predicates as string</param>
/// <param name="includeNullValues">Determines whether null values are included when constructing query</param>
/// <returns></returns>
public static Expression<Func<TDbType, bool>> BuildPredicate<TDbType>(object searchCriteria, IPredicateParser predicateParser, bool includeNullValues)
{
var filterDictionary = searchCriteria.ToFilterDictionary(includeNullValues);
return BuildPredicate<TDbType>(filterDictionary, predicateParser);
}
public static Expression<Func<TDbType, bool>> BuildPredicate<TDbType>(Dictionary<string, string> searchCriteria, IPredicateParser predicateParser)
{
var predicateOuter = PredicateBuilder.New<TDbType>(true);
var predicateErrorFields = new List<string>();
var dict = searchCriteria;// as Dictionary<string, string>;
if (dict == null || !dict.Any())
return predicateOuter;
var searchFields = typeof(TDbType).GetProperties();
foreach (var searchField in searchFields)
{
// Get the name of the DB field, which may not be the same as the property name.
var dbFieldName = GetDbFieldName(searchField);
var dbType = typeof(TDbType);
var dbFieldMemberInfo = dbType.GetMember(dbFieldName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).SingleOrDefault();
if (dbFieldMemberInfo == null || !dict.ContainsKey(dbFieldMemberInfo.Name))
continue;
var predicateValue = dict[dbFieldMemberInfo.Name];
if (predicateValue == null)
continue;
var rangesAllowed = searchField.PropertyType != typeof(string);
if (!predicateParser.Parse(predicateValue, rangesAllowed, searchField.PropertyType))
{
predicateErrorFields.Add(dbFieldMemberInfo.Name);
continue;
}
if (!predicateParser.Items.Any())
continue;
var predicateInner = BuildInnerPredicate<TDbType>(predicateParser, searchField, dbFieldMemberInfo);
if (predicateInner == null)
continue;
predicateOuter = predicateOuter.And(predicateInner);
}
return predicateOuter;
}
private static Expression<Func<TDbType, bool>> BuildInnerPredicate<TDbType>(IPredicateParser predicateParser, PropertyInfo searchField, MemberInfo dbFieldMemberInfo)
{
var dbType = typeof(TDbType);
// Create an "x" as TDbType
var dbTypeParameter = Expression.Parameter(dbType, #"x");
// Get at x.firstName
var dbFieldMember = Expression.MakeMemberAccess(dbTypeParameter, dbFieldMemberInfo);
Expression<Func<TDbType, bool>> predicateInner = null;
foreach (var predicateItem in predicateParser.Items)
{
var predicateItemSingle = predicateItem as PredicateItemSingle;
var predicateItemRange = predicateItem as PredicateItemRange;
if (predicateItemSingle != null)
{
// Create the MethodCallExpression like x.firstName.Contains(criterion)
if (searchField.PropertyType == typeof(string))
{
var str = predicateItemSingle.Value as string ?? "";
var startsWithAsterisk = str.StartsWith("*");
var endsWithAsterisk = str.EndsWith("*");
str = str.Trim('*').Trim();
MethodCallExpression callExpression;
if (startsWithAsterisk && !endsWithAsterisk)
{
callExpression = Expression.Call(dbFieldMember, StringEndsWithMethod, new Expression[] { Expression.Constant(str) });
}
else if (!startsWithAsterisk && endsWithAsterisk)
{
callExpression = Expression.Call(dbFieldMember, StringStartsWithMethod, new Expression[] { Expression.Constant(str) });
}
else
{
callExpression = Expression.Call(dbFieldMember, StringContainsMethod, new Expression[] { Expression.Constant(str) });
}
predicateInner = (predicateInner ?? PredicateBuilder.New<TDbType>(false)).Or(Expression.Lambda(callExpression, dbTypeParameter) as Expression<Func<TDbType, bool>>);
}
else
{
if (dbFieldMember.Type.IsEnum)
{
if (!dbFieldMember.Type.IsEnumDefined(predicateItemSingle.Value))
continue;
var enumValue = (int)predicateItemSingle.Value;
if (enumValue <= 0)
continue;
var enumObj = Enum.ToObject(dbFieldMember.Type, (int)predicateItemSingle.Value);
predicateInner = (predicateInner ?? PredicateBuilder.New<TDbType>(false)).Or(Expression.Lambda<Func<TDbType, bool>>(Expression.Equal(dbFieldMember, Expression.Constant(enumObj)), new[] { dbTypeParameter }));
}
else
{
predicateInner = (predicateInner ?? PredicateBuilder.New<TDbType>(false)).Or(Expression.Lambda<Func<TDbType, bool>>(Expression.Equal(dbFieldMember, Expression.Constant(predicateItemSingle.Value)), new[] { dbTypeParameter }));
}
}
}
else if (predicateItemRange != null)
{
var predicateRange = PredicateBuilder.New<TDbType>(true);
predicateRange = predicateRange.And(Expression.Lambda<Func<TDbType, bool>>(Expression.GreaterThanOrEqual(dbFieldMember, Expression.Constant(predicateItemRange.Value1)), new[] { dbTypeParameter }));
predicateRange = predicateRange.And(Expression.Lambda<Func<TDbType, bool>>(Expression.LessThanOrEqual(dbFieldMember, Expression.Constant(predicateItemRange.Value2)), new[] { dbTypeParameter }));
predicateInner = (predicateInner ?? PredicateBuilder.New<TDbType>(false)).Or(predicateRange);
}
}
return predicateInner;
}
private static string GetDbFieldName(PropertyInfo propertyInfo)
{
var dbFieldName = propertyInfo.Name;
// TODO: Can put custom logic here, to obtain another field name if desired.
return dbFieldName;
}
}
}
Usage
Let's say we have the DbPerson class that holds our data:
public class DbPerson
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public int Age { get; set; }
}
And beside that DbPerson class we have a class that represents our filter for the DbPerson objects:
public class DbPersonFilter
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string BirthDate { get; set; }
public string Age { get; set; }
}
Notice how the names of properties for base class DbPerson and DbPersonFilter are the same. This is important because a lot of code above requires that naming convention is consistent. Type of properties, however are not the same. This is because for filter we can set ranges to search, not just one value. Later there will be some samples to see how this works.
Now, let us fill our "database" with simple data. We use this method:
private List<DbPerson> GenerateTestDb()
{
var result = new List<DbPerson>
{
new DbPerson { Id = 1,FirstName = "John", LastName = "Doe", BirthDate = new DateTime(1963, 6, 14), Age = 53 },
new DbPerson { Id = 2,FirstName = "Jane", LastName = "Hunt", BirthDate = new DateTime(1972, 1, 16), Age = 44 },
new DbPerson { Id = 3,FirstName = "Aaron", LastName = "Pitch", BirthDate = new DateTime(1966, 7, 31), Age = 50 },
};
return result;
}
Using clauses for our sample applications are the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using LambdaSample.Extensions;
using LambdaSample.Framework;
using LinqKit;
Now, lets create some btnTest in our WinForms application (of course, you would you this in your app, whatever may be):
private void btnTest_Click(object sender, EventArgs e)
{
// Load sample database into db (db is actually List<DbPerson>)
var db = GenerateTestDb();
// Create filter looking for FirstName is "John"
var filterValues = new DbPersonFilter
{
FirstName = "John",
};
// Build PredicateParser which it used to parse predicates inside ExpressionExtensions.
var predicateParser = new PredicateParser();
// Build predicate...
var predicate1 = PredicateBuilder.New(ExpressionExtensions.BuildPredicate<DbPerson>(filterValues, predicateParser, true));
// And search for items...
var items1 = db.AsQueryable().AsExpandable().Where(predicate1).ToList();
// Create filter to look for items where Id is between 1 and 2
filterValues = new DbPersonFilter
{
Id = "1-2",
};
// Build predicate...
var predicate2 = PredicateBuilder.New(ExpressionExtensions.BuildPredicate<DbPerson>(filterValues, predicateParser, true));
// And search for items...
var items2 = db.AsQueryable().AsExpandable().Where(predicate2).ToList();
// Create filter to look for items where Age is 44
filterValues = new DbPersonFilter
{
Age = "44",
};
// Build predicate...
var predicate3 = PredicateBuilder.New(ExpressionExtensions.BuildPredicate<DbPerson>(filterValues, predicateParser, true));
// And search for items...
var items3 = db.AsQueryable().AsExpandable().Where(predicate3).ToList();
}
Hope this helps. Code should be self-explanatory, since comments are not included everywhere. If you have any more questions, do ask.
NOTE: .AsExpandable() is and extension method of LinqKit, to use PredicateBuilder inside Where extension method.
I deliberatly left out details about the actual domain in which I'm working. This was an attempt to make my question more generic and focused on Expressions.
But as it turned out there was a FilterExpressionParser available in the API I'm using (Episerver FIND) which came in handy.
So here's a function that builds and applies a composite filter.
private void MechanicalPropertiesFilter(SteelNavigatorForm form, ref ITypeSearch<SteelGradeVariantPage> search)
{
FilterExpressionParser filterExpressionParser = new FilterExpressionParser(SearchClient.Instance.Conventions);
Filter combinedFilter = null;
// Dimension
if (form.DimensionThickness > 0)
{
var dimensionFilter = filterExpressionParser.GetFilter<MechanicalProperties>(m => m.DimensionInMillimeterMin.LessThan(form.DimensionThickness)
& m.DimensionInMillimeterMax.GreaterThan(form.DimensionThickness));
combinedFilter = (combinedFilter == null) ? dimensionFilter : combinedFilter & dimensionFilter;
}
// Yield strength
if (form.YieldStrengthMin > 0)
{
var yieldStrengthFilter = filterExpressionParser.GetFilter<MechanicalProperties>(m => m.YieldStrengh.GreaterThan(form.YieldStrengthMin));
combinedFilter = (combinedFilter == null) ? yieldStrengthFilter : combinedFilter & yieldStrengthFilter;
}
// Tensile strength
if (form.TensileStrengthMin > 0 | form.TensileStrengthMax > 0)
{
var tensileStrengthMin = (form.TensileStrengthMin == 0) ? double.MinValue : form.TensileStrengthMin;
var tensileStrengthMax = (form.TensileStrengthMax == 0) ? double.MaxValue : form.TensileStrengthMax;
var tensileStrengthFilter = filterExpressionParser.GetFilter<MechanicalProperties>(m => m.TensileStrengthMin.InRangeInclusive(tensileStrengthMin, tensileStrengthMax) | m.TensileStrengthMax.InRangeInclusive(tensileStrengthMin, tensileStrengthMax));
combinedFilter = (combinedFilter == null) ? tensileStrengthFilter : combinedFilter & tensileStrengthFilter;
}
// Elongation
if (form.Elongation > 0)
{
var elongationFilter = filterExpressionParser.GetFilter<MechanicalProperties>(m => m.ElongationA5Percentage.GreaterThan(form.Elongation));
combinedFilter = (combinedFilter == null) ? elongationFilter : combinedFilter & elongationFilter;
}
// Hardness
if (form.HardnessMin > 0 || form.HardnessMax > 0)
{
var max = (form.HardnessMax == 0) ? double.MaxValue : form.HardnessMax;
var hardnessFilter = filterExpressionParser.GetFilter<MechanicalProperties>(m => m.HardnessScaleGuid.Match(form.HardnessMethod) & (
m.HardnessMin.InRangeInclusive(form.HardnessMin, max)
| m.HardnessMax.InRangeInclusive(form.HardnessMin, max)));
combinedFilter = (combinedFilter == null) ? hardnessFilter : combinedFilter & hardnessFilter;
}
if (combinedFilter != null)
{
NestedFilterExpression<SteelGradeVariantPage, MechanicalProperties> mechanicalFilterExpression = new NestedFilterExpression<SteelGradeVariantPage, MechanicalProperties>(v => v.MechanicalProperties, ((MechanicalProperties item) => combinedFilter), search.Client.Conventions);
search = search.Filter(mechanicalFilterExpression.NestedFilter);
}
}

Cannot Convert Type Void to Generic List

I have two lists: a list of countries and a list of jobs
public List<Countries> getSharedCountries(string brandName)
{
var items = SharedJobs.Where(a => a.BrandName == brandName);
var items2 = items.OrderBy(a => a.CountryCode);
Countries = new List<Countries>();
string Country = null;
foreach (var item in items2)
{
if (Country != item.CountryCode)
{
Country = item.CountryCode;
Countries.Add(new Countries() { CountryCode = item.CountryCode, JobIDs = getSharedJob(item.CountryCode) });
}
}
return Countries;
}
public void getSharedJob(string Country)
{
var items = SharedJobs.Where(a => a.CountryCode == Country);
JobNetDetails = new List<JobDetail>();
CareerBoardDetails = new List<JobDetail>();
JobSharkDetails = new List<JobDetail>();
JobServeDetails = new List<JobDetail>();
int AusCount = 0;
foreach (var item in items)
{
if (Country == "AUS")
{
AusCount++;
if (AusCount % 4 == 0)
{
JobNetDetails.Add(new JobDetail() { JobPageTitle = item.JobPageTitle, JobID = item.JobID, JobUrl = item.JobUrl });
}
else
{
JobServeDetails.Add(new JobDetail() { JobPageTitle = item.JobPageTitle, JobID = item.JobID, JobUrl = item.JobUrl });
}
}
}
}
On the line where I am accessing the method getSharedJob, it errors and gives me the error, cannot implicitly convert void to system.generic.List?
I am very confused as to why this is happening?
As the signature of your method states, public void getSharedJob(string Country) it's void, so it doesn't return anything, you should change it and return the list you wish.
Edit: As I read in the comments you need to return 4 Lists.
You have several options:
You can return an array of Lists;
You can return a List of Lists;
You can return your own class containing the 4 Lists.
Try below code which returns jobDetails from the method you are calling
public List<Countries> getSharedCountries(string brandName)
{
var items = SharedJobs.Where(a => a.BrandName == brandName);
var items2 = items.OrderBy(a => a.CountryCode);
Countries = new List<Countries>();
string Country = null;
foreach (var item in items2)
{
if (Country != item.CountryCode)
{
Country = item.CountryCode;
foreach (var jobDetail in getSharedJob(item.CountryCode))
{
Countries.Add(new Countries() { CountryCode = item.CountryCode, JobIDs = jobDetail.JobID });
}
}
}
return Countries;
}
public List<JobDetail> getSharedJob(string Country)
{
var items = SharedJobs.Where(a => a.CountryCode == Country);
JobNetDetails = new List<JobDetail>();
CareerBoardDetails = new List<JobDetail>();
JobSharkDetails = new List<JobDetail>();
JobServeDetails = new List<JobDetail>();
int AusCount = 0;
foreach (var item in items)
{
if (Country == "AUS")
{
AusCount++;
if (AusCount % 4 == 0)
{
JobNetDetails.Add(new JobDetail() { JobPageTitle = item.JobPageTitle, JobID = item.JobID, JobUrl = item.JobUrl });
}
else
{
JobServeDetails.Add(new JobDetail() { JobPageTitle = item.JobPageTitle, JobID = item.JobID, JobUrl = item.JobUrl });
}
}
}
return JobServeDetails;
}
Your method signature says take a string variable and return nothing (void).
public void getSharedJob(string country)
JobIDs is expecting a value
JobIDs = getSharedJob(item.CountryCode)
so you need to return a value which matches the JobIDs type which I assume is a List of ints or a List of JobDetails.

Linq query EntityCommandExeceptionException error

When I call 'api/test/name=stop,tap,app...(24 names values)' from the following query below, I am experiencing the an error:
"Message":"An error has occurred.","ExceptionMessage":"Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.","ExceptionType":"System.Data.SqlClient.SqlException
Linq Query:
var data = db.database_bd.AsQueryable();
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
// If any other filters are specified, return records which match any of them:
var filteredData = new List<IQueryable<database_bd>>();
if (!string.IsNullOrEmpty(query.name))
{
var ids = query.name.Split(',');
foreach (string i in ids)
{
filteredData.Add(data.Where(c => c.Name != null && c.Name.Contains(i)));
}
}
// If no filters passed, return all data.
// Otherwise, combine the individual filters using the Union method
// to return all records which match at least one filter.
if (filteredData.Count != 0)
{
data = filteredData.Aggregate(Queryable.Union);
}
if (!data.Any()) //line causing error
{
var message = string.Format("No data was found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, data);
}
Query class:
public class Query
{
public string name { get; set; }
public Nullable<DateTime> startDate { get; set; }
public Nullable<DateTime> endDate { get; set; }
}
I tried adding a range to the 'filterdata' method, but I could not get that to work. Any advice, would be very much appreciated.
Thanks
This doesn't work?
var data = db.database_bd.AsQueryable();
if (query.startDate != null)
{
data = data.Where(c => c.UploadDate >= query.startDate);
}
if (!string.IsNullOrEmpty(query.name))
{
var list = query.name.Split(',');
data = data.Where(pr => list.Any(pr2 => pr.Name.Contains(pr2)));
}
I would use predicatebuilder for this (look at doc for "installation").
var data = db.database_bd.AsQueryable();
var mainPredicate = PredicateBuilder.True<database_bd>();
if (query.startDate != null)
mainPredicate = mainPredicate.And(c => c.UploadDate >= query.startDate);
if (!string.IsNullOrEmpty(query.name))
{
var namePredicate = PredicateBuilder.False<database_bd>();
var ids = query.name.Split(',');
foreach (var id in ids) {
namePredicate = namePredicate.Or(c => c.Name != null && c.Name.Contains(id));
}
mainPredicate = mainPredicate.And(namePredicate);
}
data = data.Where(mainPredicate );
if (!data.Any()) //line hopefully causing no more error
{
var message = string.Format("No data was found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}

Categories

Resources