C# Predicate builder with using AND with OR - c#

I have the following class:
public class testClass
{
public string name { get; set; }
public int id { get; set; }
public int age { get; set; }
}
and the following code:
var list = new List<testClass>();
list.Add(new testClass { name = "name", id = 1, age = 30 });
list.Add(new testClass { name = "name", id = 2, age = 22 });
list.Add(new testClass { name = "name", id = 3, age = 20 });
list.Add(new testClass { name = "name", id = 4, age = 30 });
list.Add(new testClass { name = "name", id = 5, age = 27 });
list.Add(new testClass { name = "name", id = 6, age = 30 });
var qble = list.AsQueryable();
var pred = PredicateBuilder.New<testClass>();
pred.Or(x => x.name == "name" && x.id == 1);
pred.Or(x => x.age == 30);
var predQuery = qble.AsExpandable().Where(pred);
My aim is to create a query that returns all records where:
id = 1 and name = "name"
OR
age = 30
So for the query above, it should return the items at index 0, 1, 5
For the above query it does as I want.
However, I now want to the build the predicate by combining a set of queries, rather than explicitly defining them. So I now have the following 2 queries:
var query1 = list.Where(x => x.name == "name" && x.id == 1);
var query2 = list.Where(x => x.age == 30);
and I want to build the query based on the variables query1 and query2, without explicitly defining the conditions - as these conditions will be dynamically defined and I do not know what they are,and they will be defined in different places.
My guess is I need to do something like this (continuing from above):
var qble = list.AsQueryable();
var query1 = list.Where(x => x.name == "name" && x.id == 1);
var query2 = list.Where(x => x.age == 30);
var pred = PredicateBuilder.New<testClass>();
pred.Or(query1);
pred.Or(query2);
var predQuery = qble.AsExpandable().Where(pred);
but this is not quite correct as the predicate builder will not accept the query as a parameter.
Can this be done?

You could create two Predicate<T> and invoke them in your .Where call at the end.
var qble = list.AsQueryable();
var query1 = new Predicate<testClass>(x => x.name == "name" && x.id == 1);
var query2 = new Predicate<testClass>(x => x.age == 30);
var predQuery = qble.AsExpandable().Where(x => query1(x) || query2(x));
Or you could build another Predicate<T> beforehand and use this
var query = new Predicate<testClass>(x => query1(x) || query2(x));
var predQuery = qble.AsExpandable().Where(query);

Related

How to get two fields with the same Id

I need to send two fields with the same Id in Altair(GraphQl).
mutation{
createGoodsOrder(goodsorder: {
deliveryDate: "2019-10-10"
goodsOrderItems: [
{ orderItemId: 54 quantity: 1 costPerUnit: 1 goodType: INGREDIENT }
{ orderItemId: 54 quantity: 2 costPerUnit: 2 goodType: INGREDIENT }
# { orderItemId: 58 quantity: 2 costPerUnit: 2 goodType: INGREDIENT }
]
}){
id
}
}
When I execute mutation, model contains both fields with the same Id but when I make Fetch, it returns only the first one. If It is not the same, Fetch returns both fields. How can I get both fields with the same Id?
var orderIngredients = _repository.Fetch<Ingredient>(e => model.GoodsOrderItems.Any(g => g.OrderItemId == e.Id)).ToList();
var orderIngredients = _repository.Fetch<Ingredient>(
e => e.IngredientType.PlaceId == model.PlaceId
&& model.GoodsOrderItems.Any(g => g.OrderItemId == e.Id && g.GoodType == GoodsTypes.Ingredient))
.Select(e => new GoodsOrderIngredientCreateModel
{
IngredientId = e.Id,
Quantity = model.GoodsOrderItems.First(i => i.OrderItemId == e.Id).Quantity,
CostPerUnit = model.GoodsOrderItems.First(i => i.OrderItemId == e.Id).CostPerUnit,
TotalPrice = model.GoodsOrderItems.First(i => i.OrderItemId == e.Id).Quantity *
model.GoodsOrderItems.First(i => i.OrderItemId == e.Id).CostPerUnit,
GoodType = GoodsTypes.Ingredient
}).Select(v => new GoodsOrderIngredient
{
Id = v.Id,
IngredientId = v.IngredientId,
Quantity = v.Quantity,
CostPerUnit = v.CostPerUnit,
TotalPrice = v.TotalPrice
}).ToList();
Mutation:
mutation.Field<GoodsOrderType>(
name: "createGoodsOrder",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<GoodsOrderCreateInput>> { Name = nameof(GoodsOrder).ToLower() }
),
resolve: context =>
{
if (context.UserContext is GraphQLUserScopedContext userContext)
{
var goodsOrderService = userContext.ServiceScope.ServiceProvider.GetRequiredService<IVendorService>();
var model = context.GetArgument<GoodsOrderCreateModel>(nameof(GoodsOrder).ToLower());
model.PlaceId = userContext.User.PlaceId;
model.NetworkId = userContext.User.NetworkId;
var goodsOrder = goodsOrderService.CreateGoodsOrder(model);
return goodsOrder;
}
else
throw new ExecutionError(Constants.ErrorCodes.WrongUserContext);
}).RequireAuthorization(PermissionsRequirement
.CreateForPermissionSetAll(
new Dictionary<NetworkPermissions, PermissionLevels>
{ {NetworkPermissions.ERP_Cumulative, PermissionLevels.EditCreate} }));
I don't know c# but probably you don't need intermediate types
var orderIngredients = _repository.Fetch<Ingredient>(
e => e.IngredientType.PlaceId == model.PlaceId
&& model.GoodsOrderItems.Any(g => g.OrderItemId == e.Id && g.GoodType == GoodsTypes.Ingredient))
.Select(v => new GoodsOrderIngredient
{
Id = v.Id,
IngredientId = v.IngredientId,
Quantity = v.Quantity,
CostPerUnit = v.CostPerUnit,
TotalPrice = v.Quantity * v.CostPerUnit
}).ToList();
PS. If GoodsOrderIngredientCreateModel (for create mutation?) contains TotalPrice then total calculations are already in DB ?

Combining LINQ queries with method syntax

I'm looking to merge the following queries into a single call to the database.
string name = Item
.Where(x => x.ItemID == 180)
.Select(x => x.ItemName).FirstOrDefault();
int itemID = Item
.Where(x => x.ItemName == name && x.Other == 50)
.Select(x => x.ItemID).FirstOrDefault();
It's pretty much getting the name using it's ID, and using that name to get the ID of another row with the same name. The "Other" in this case narrows down the second query to a single result in this simplified example.
This should work:
int itemID = Item.Where(x => x.ItemName == Item.Where(y => y.ItemID == 180)
.Select(y => y.ItemName).FirstOrDefault() && x.Other == 50)
.Select(x=>x.ItemID)
.FirstOrDefault();
Here is a full working example if you choose to use a Join.
Example Data:
List<Item> Items = new List<Item>()
{
new Item { ItemId = 1, ItemName = "Item1", Other = 1 },
new Item { ItemId = 2, ItemName = "Item2", Other = 2 },
new Item { ItemId = 3, ItemName = "Item3", Other = 3 },
new Item { ItemId = 4, ItemName = "Item4", Other = 4 },
new Item { ItemId = 5, ItemName = "Item5", Other = 5 },
new Item { ItemId = 6, ItemName = "Item6", Other = 6 },
new Item { ItemId = 7, ItemName = "MyExpectedName", Other = 50 },
new Item { ItemId = 8, ItemName = "MyExpectedName", Other = 50 },
new Item { ItemId = 180, ItemName = "MyExpectedName", Other = 8 },
};
Example query with a Join:
var itemId = Items
.Join(Items.Where(a => a.ItemId == 180), x => x.ItemName, y => y.ItemName, (x, y) => x)
.Where(x => x.Other == 50)
.Select(x => x.ItemId)
.FirstOrDefault();
Note that you will get a different ItemID than from the provided ItemID of 180 in this example. In this case, since I statically created this list as an example, I know that I will get ItemID of 7 returned every time. However, if this query is being run against an underlying database it is possible that the database won't return the items ordered by the ItemID (unless you tell it explicitly to do so using the OrderedBy clause). So it is possible that ItemId = 8 could be returned from the database as the first record set rather than 7.
This finds the ItemName of the item with ItemID 180 and uses it to filter all items by this value.
int itemID = (from i in Item
where i.ItemName = (
from j in Item
where j.ItemID == 180
select j.ItemName).FirstOrDefault()
&& j.Other == 50
select i.ItemID).FirstOrDefault();
This is in query syntax which I find much easier to read, but it's not to complex to convert to method call syntax if you prefer. ReSharper can do it automagically if you have it.
It can be simplified:
var name = Items.FirstOrDefault(x => x.ItemID == 180)?.ItemName;
var id = Items.FirstOrDefault(x => x.ItemName == name && x.Other == 50)?.ItemID;
Something like this:
Item
.Join(Item, x => x.Name, x => x.Name, (x1, x2) => new {x1, x2})
.FirstOrDefault(arg => arg.x1.ItemID == 180 && arg.x2.Other == 50)?.x2.ItemID;
But do you realy need single query?

Select Last non null-able item per product?

Let's say I have,
class Product
{
public int Id {get; set;}
public string Name {get; set;}
public int Order {get; set;}
}
and my data have,
products[0] = new Product { Id = 1, Name = "P1", Order = 1 };
products[1] = new Product { Id = 1, Name = "P2", Order = 2 };
products[2] = new Product { Id = 1, Name = null, Order = 3 };
products[3] = new Product { Id = 2, Name = "P3", Order = 4 };
products[4] = new Product { Id = 2, Name = null, Order = 5 };
products[5] = new Product { Id = 2, Name = null, Order = 6 };
What I need is the last(order by Order desc) non-nullable value of Name per Product.Id. So my final output will look like,
items[0] = new { Id = 1, Name = "P2"};
items[1] = new { Id = 2, Name = "P3"};
If Id=1, I have 3 Names (P1, P2, null) and non-nullable Names (P1, P2) but last one is P3.
This should get the last products in order.
var lastOrders = products
.Where(x => x.Name != null) // Remove inapplicable data
.OrderBy(x => x.Order) // Order by the Order
.GroupBy(x => x.Id) // Group the sorted Products
.Select(x => x.Last()); // Get the last products in the groups
var result = products
.GroupBy(p => p.Id)
.Select(g => g.OrderBy(x => x.Order).Last(x => x.Name != null));
this will give you your desired output:
products.GroupBy(p => p.Id)
.Select(g => g.OrderByDescending(gg => gg.Name)
.Where(gg => gg.Name != null)
.Select(gg => new { gg.Id, gg.Name })
.First());
The task can be solved using the following Linq statement.
var Result = products.OrderBy().Where( null != iProduct.Name ).First();
This requires products to contain at least one item where Name is null, otherwise an Exception will be thrown. Alternatively,
var Result = products.OrderBy().Where( null != iProduct.Name ).FirstOrDefault();
will return null if products contains no such item.
Try with :
var expectedProduct =products.Where(p => p.Id != null).OrderByDescending(p => p.Order).GroupBy(p => p.Id).Last()

Assigning a local variable within a lambda expression

I have a lambda expression in which need to assign a local variable to avoid calling my RetrieveAge(Datetime birthDate) two times per resultset.
My lambda expression looks as following:
result = myList.AsEnumerable().Where(f => DateHelper.RetrieveAge(f.Birthdate) >= 20 && DateHelper.RetrieveAge(f.Birthdate) <= 40).Select(x => new Person { Name = x.Name, Id = x.Id, Alias = x.Alias }).ToList();
I am trying to achieve something like the following:
var result = myList.AsEnumerable().Where(f => { var age = DateHelper.RetrieveAge(f.Birthdate); age >= 20 && age <= 40 }).Select(x => new Person { Name = x.Name, Id = x.Id, Alias = x.Alias }).ToList();
But I can't figure out how to do that properly. Any hints or suggestions would be mostly appreciated.
You are almost there - you need to add a return and a semicolon:
var result = myList
.AsEnumerable()
.Where(f => {
var age = DateHelper.RetrieveAge(f.Birthdate);
return age >= 20 && age <= 40; // <<== Here
}).Select(x => new Person {
Name = x.Name, Id = x.Id, Alias = x.Alias }
).ToList();
var result = (from f in myList.AsEnumerable()
let age = DateHelper.RetrieveAge(f.Birthdate)
where age >= 20 && age <= 40
select new Person { Name = f.Name, Id = f.Id, Alias = f.Alias }).ToList();
var result = myList.AsEnumerable()
.Select(f => new { F = f, X = DateHelper.RetrieveAge(f.Birthdate))
.Where(f => f.F.age >= 20 && f.F.age <= 40 })
.Select(x => new Person { Name = f.F.Name, Id = f.F.Id, Alias = f.F.Alias })
.ToList();
var result = myList.AsEnumerable().Where(f =>
{
var age = DateHelper.RetrieveAge(f.Birthdate);
return age >= 20 && age <= 40
}).Select(x => new Person { Name = x.Name, Id = x.Id, Alias = x.Alias }).ToList();

conditional Updating a list using LINQ

I had a list
List<Myclass> li = new List<Myclass>();
where Myclass is
class Myclass
{
public string name {get;set;}
public decimal age {get;set;}
}
items in li looks like
i want to update `li` according to name but with `LINQ` like
li.where(w=> w.name = "di") = li.Where(w => w.name =="di").select(s => {s.age = 10;return s;}).Tolist();
li.where(w=> w.name = "marks") = li.Where(w => w.name =="marks").select(s => {s.age = 20;return s;}).Tolist();
li.where(w=> w.name = "grade") = li.Where(w => w.name =="grade").select(s => {s.age = 10;return s;}).Tolist();
and want result which looks like this
my code gives error can you please tell how i do this
cleaner way to do this is using foreach
foreach(var item in li.Where(w => w.name =="di"))
{
item.age=10;
}
You need:
li.Where(w=> w.name == "di").ToList().ForEach(i => i.age = 10);
Program code:
namespace Test
{
class Program
{
class Myclass
{
public string name { get; set; }
public decimal age { get; set; }
}
static void Main(string[] args)
{
var list = new List<Myclass> { new Myclass{name = "di", age = 0}, new Myclass{name = "marks", age = 0}, new Myclass{name = "grade", age = 0}};
list.Where(w=> w.name == "di").ToList().ForEach(i => i.age = 10);
list.ForEach(i => Console.WriteLine(i.name + ":" + i.age));
}
}
}
Output:
di:10
marks:0
grade:0
li.Where(w => w.name == "di" )
.Select(s => { s.age = 10; return s; })
.ToList();
Try this:
li.ForEach(x => x.age = (x.name == "di") ?
10 : (x.name == "marks") ?
20 : (x.name == "grade") ?
30 : 0 );
All values are updated in one line of code and you browse the List only ONE time. You have also a way to set a default value.
If you really want to use linq, you can do something like this
li= (from tl in li
select new Myclass
{
name = tl.name,
age = (tl.name == "di" ? 10 : (tl.name == "marks" ? 20 : 30))
}).ToList();
or
li = li.Select(ex => new MyClass { name = ex.name, age = (ex.name == "di" ? 10 : (ex.name == "marks" ? 20 : 30)) }).ToList();
This assumes that there are only 3 types of name. I would externalize that part into a function to make it more manageable.
Try Parallel for longer lists:
Parallel.ForEach(li.Where(f => f.name == "di"), l => l.age = 10);
How about
(from k in myList
where k.id > 35
select k).ToList().ForEach(k => k.Name = "Banana");

Categories