switch case inside linq - c#

I have a linq query like
var tmp = (from t in db.sometable
where (bunch of conditions)
group t by new { t.somefield} into g
select new
{
Name = g.Key.someobject.Name,
xColor = g.Sum(a => (int?)a.LineItems.Where(m => m.Product == "x" && m.Color == true)
.Sum(m => m.Quantity)),
xBW = g.Sum(a => (int?)a.LineItems.Where(m => m.Product == "x" && m.Color == false)
.Sum(m => m.Quantity))
})
Now I want to optimize this query. If I had to write a stored procedure I would have written some thing like:
if(a.LineItems.Product == 'x')
{
if(m.Color == true)
xColor++;
else
xBW++;
}
so that I get aggregated value in one scan. Basically I wanted to optimize this query to avoid multiple scans.

check out this simple code snippet in Linqpad, which does the way you expect, for running in visual studio, remove the Dump method call. I have simplified the class structure a bit for a simple demo, it is just a POCO, not a complex type containing another list inside
void Main()
{
var testList = Test.CreateList();
var tmp = (from t in testList
group t by new { t.Name } into g
select new
{
Name = g.Key.Name,
xColor = g.Sum(a =>
{
if (a.Product == "x" && a.Color == true)
return a.Quantity;
return 0;
}),
xBW = g.Sum(a =>
{
if (a.Product == "x" && a.Color == false)
return a.Quantity;
return 0;
})
});
tmp.Dump();
}
public class Test
{
public string Name { get; set; }
public string Product { get; set;}
public bool Color { get; set;}
public int? Quantity { get; set;}
public static List<Test> CreateList()
{
return new List<Test>()
{
new Test {Name="A",Color = true,Product="x",Quantity=5},
new Test {Name="A",Color = true,Product="x",Quantity=5},
new Test {Name="A",Color = true,Product="x",Quantity=5},
new Test {Name="B",Color = true,Product="x",Quantity=5},
new Test {Name="B",Color = true,Product="x",Quantity=5},
new Test {Name="B",Color = true,Product="x",Quantity=5}
};
}
}
However whether this is efficient or not can still be debated

You can do the same thing that would work in SQL. If there's a subset of data (lines matching particular criteria) then select those first into an array. Then you can execute your larger query against that subset.

Related

C# LINQ Filter list of complex objects by sub-list using a list of values

I want to return a list of active groups that are discounted in requested states. The list of groups each have a list of states which include the state abbrev and a discount flag.
filter criteria:
string[] states //list of state abbreviations
List to filter:
public class WorksiteGroup
{
public long Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public bool IsDiscontinued { get; set; }
public List<WorksiteGroupState> ActiveStates { get; set; } = new List<WorksiteGroupState>();
}
public class WorksiteGroupState
{
public string StateAbbrev { get; set; }
public bool IsDiscountApplied { get; set; }
}
Again, I want to return a list of WorksiteGroup with the full structure above where IsDiscontinued is false and have an ActiveState where StateAbbrev matches any of the filter criteria (states[]) and IsDiscountApplied is true for that state.
Let's do this step by step and then we can merge operations where necessary.
I want to return a list of WorksiteGroup with the full structure above
where IsDiscontinued is false
source.Where(e => !e.IsDiscontinued);
and have an ActiveState where StateAbbrev matches any of the filter
criteria (states[])
now let's take the previous pipeline and chain this criterion into it.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(a => states.Contains(a.StateAbbrev)))
and IsDiscountApplied is true for that state.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => states.Contains(s.StateAbbrev) && s.IsDiscountApplied));
for efficiency let's swap the Contains call to be after s.IsDiscountApplied e.g.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => s.IsDiscountApplied && states.Contains(s.StateAbbrev)));
You can try this using Linq:
string[] states = new string[] { "abbrev1", "abbrev2" };
var list = new List<WorksiteGroup>();
var item = new WorksiteGroup();
item.Name = "Test1";
item.IsDiscontinued = false;
var subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
item = new WorksiteGroup();
item.Name = "Test2";
item.IsDiscontinued = true;
subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
var result = list.Where(wg => wg.IsDiscontinued == false
&& wg.ActiveStates.Where(state => state.IsDiscountApplied == true
&& states.Contains(state.StateAbbrev)).Any());
foreach ( var value in result )
Console.WriteLine(value.Name);
Console.ReadKey();
You can play with items and add more to see results.
sudo-code but would something like below work, im sure you could do this is one line but
var worksiteGroup = Populate();
var filteredWorkSiteGroup = worksiteGroup .Where(x=>x.IsDiscontinued == false);
filteredWorkSiteGroup.ActiveStates = filteredWorkSiteGroup.ActiveStates
.Where(x=> states.Contains(x.StateAbbrev)
&& x.IsDiscountApplied == true);

Best Practice to find best matching instance in a List C#

For sure very simple question for most of you.
But I am struggling with a solution at the moment.
Imagine you have a list of cats (List) where each cat has a list of babys (Kitten)
public class Cat
{
public string Name { get; set; }
public int Age { get; set; }
public string Race { get; set; }
public bool Gender { get; set; }
public List<Kitten> Babys { get; set; }
}
public class Kitten
{
public string Name { get; set; }
public double Age { get; set; }
public bool Gender { get; set; }
}
now I want to find the Cat that has the most matches for given requirements. It could easily be the case that a cat matches only 2 of 3 requirements. I simple want to find the cat that has the most matches to my requirements.
where my requirements could be:
Name has to be "Micky"
Age is 42
Has a Kitten named "Mini"
My actual solution would be to compare all properties and take the one with the highest count of matching properties. But this is not generic and I am sure there are mutch better ways to do it.
Thanks in advance
Well, I have no opportunity to test this solution, but you can try this:
Assume that you have a list of cats:
var cats = new List<Cat>();
Now you have defined what are your criteria:
var desiredName = "Micky";
var desiredAge = 42;
var desiredKitten = "Mini";
And then you have to get your desired cat:
var desiredCat = cats
.Select(c => new {
Rating =
Convert.ToInt32(c.Age == desiredAge) + // Here you check first criteria
Convert.ToInt32(c.Name == desiredName) + // Check second
Convert.ToInt32(c.Babys.Count(b => b.Name == desiredKitten) > 0), // And the third one
c })
.OrderByDescending(obj => obj.Rating) // Here you order them by number of matching criteria
.Select(obj => obj.c) // Then you select only cats from your custom object
.First(); // And get the first of them
Please check if this works for you.
And if you need more specific answer or some edits for me to add.
If you really will compare 2 ou 3 requirements you can simplify using Linq by:
// try to find with 3 requirements
var foundCats = catList.Where(t => t.Name == desiredName &&
t.Age == desiredAge &&
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with 2 requirements
foundCats = catList.Where(t =>
(t.Name == desiredName && t.Age == desiredAge) ||
(t.Name == desiredName && t.Babys.Any(k => k.Name == desiredKitten)) ||
(t.Age == desiredAge && t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with only 1 requirement
foundCats = catList.Where(t => t.Name == desiredName ||
t.Age == desiredAge ||
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
return foundCats;
So, I see that the problem is you don't know if in any near future you will have more properties, so I will suggest going to the hardway and make reflection, the following is ugly af but you can probably (you will) make it better and hopefully serves you well as guiadance:
public static List<Cat> CheckProperties(List<Cat> inCatList, Cat inQueryCat)
{
Dictionary<Cat, List<PropertyInfo>> dict = new Dictionary<Cat, List<PropertyInfo>>();
foreach (PropertyInfo pI in inQueryCat.GetType().GetProperties())
{
var value = pI.GetValue(inQueryCat);
if (value != null)
{
var cats = inCatList.Where(cat => cat.GetType().GetProperty(pI.Name).GetValue(cat).Equals(value));
foreach (Cat cat in cats)
{
if (dict.ContainsKey(cat))
{
dict[cat].Add(pI);
}
else
{
dict.Add(cat, new List<PropertyInfo>() {pI});
}
}
}
}
int max = Int32.MinValue;
foreach (KeyValuePair<Cat, List<PropertyInfo>> keyValuePair in dict)
{
if (keyValuePair.Value.Count > max)
{
max = keyValuePair.Value.Count;
}
}
return dict.Where(pair => pair.Value.Count == max).Select(pair => pair.Key).ToList();
}
While this is the most generic solution there is (need some edge case improvements):
public class ReflectCmpare
{
public PropertyInfo PropertyInfo { get; set; }
public dynamic Value { get; set; }
}
public Cat GetBestCat(List<Cat> listOfCats, List<ReflectCmpare> catParamsToCompare, List<ReflectCmpare> kittensParamsToCompare)
{
var bestScore = 0;
var ret = listOfCats[0];
foreach (var cat in listOfCats)
{
var score = catParamsToCompare.Sum(param => param.PropertyInfo.GetValue(cat, null) == param.Value ? 1 : 0);
foreach (var baby in cat.Babys)
{
score+= kittensParamsToCompare.Sum(param => param.PropertyInfo.GetValue(baby, null) == param.Value ? 1 : 0);
}
if (score <= bestScore) continue;
bestScore = score;
ret = cat;
}
return ret;
}
You should really think about just doing simple compare function
considering this objects is not dynamic this is the way to go:
public Cat GetBestCat(List<Cat> listOfCats, string name , int? age , bool? gender, string race ,string babyName,int? babyAge,bool? babyGender )
{
var ret = listOfCats[0];
var highestScore = 0;
foreach (var cat in listOfCats)
{
var score = 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += age.HasValue && cat.Age.Equals(age.Value) ? 1 : 0;
score += gender.HasValue && cat.Gender.Equals(gender.Value) ? 1 : 0;
score += race != null && cat.Race.Equals(race) ? 1 : 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += cat.Babys
.Where(k => babyName==null || k.Name.Equals(babyName))
.Where(k => !babyAge.HasValue || k.Age.Equals(babyAge.Value))
.Any(k => !babyGender.HasValue || k.Gender.Equals(babyGender.Value))?1:0;
if (score <= highestScore) continue;
highestScore = score;
ret = cat;
}
return ret;
}

Entity framework use between with strings

I realy stucked with my function. I have table and I'm using EF. Representation for this table in ORM is:
public partial class ProductAttributes
{
public long Id { get; set; }
public System.Guid AttrId { get; set; }
public System.Guid ProdId { get; set; }
public string Value { get; set; }
public virtual Attributes Attributes { get; set; }
public virtual Products Products { get; set; }
}
This table contains FK to products and Attributes. Main responsibility of this table is to keep value for specified product and attribute. As you can see value represented as string.
Users on web site using sliders sets range for values. Now I should get all products from DB where values between users choice. Also a choose can be not range, just single value represented as a string. And here I got stuck.
Here function for selecting values from DB:
// Dictionaries Guid - attributeID , string[] - values from slider
public IEnumerable<Products> GetFilteredProducts(Dictionary<Guid, string[]> searchParam)
{
List<Products> list = new List<Products>();
try {
entityContext = new SiteDBEntities();
IQueryable<Products> query = entityContext.Products;
foreach (var item in searchParam)
{
//check do we have single value, it's mean that we whould search equals value
if (item.Value.Length == 1)
{
var r = item.Value[0];
query = query.Where(x => x.ProductAttributes
.Any(y => y.AttrId == item.Key && y.Value.Equals(r)));
}
else
{
double a = Double.Parse(item.Value[0]); // min value
double b = Double.Parse(item.Value[1]); // max value
//AND HERE BECOMES BIG PROBLEMS
// this code will throw error becuase ToDouble cannot be converted to T-SQl
// query = query.Where(x => x.ProductAttributes
// .Any(y => y.AttrId == item.Key
// && Convert.ToDouble(y.Value) > a
// && Convert.ToDouble(y.Value) < b));
//this will return error cannot conver string to double
// query = query.Where(x => x.ProductAttributes
// .Any(y => y.AttrId == item.Key
// && (double)y.Value > a
// && (double)y.Value < b));
// this will return error 'cannot apply ">" to IEnumerable<double>'
// query = query.Where(x => x.ProductAttributes.
// Any(y => y.AttrId == item.Key
// && y.Value.Cast<double>() > a
// && y.Value.Cast<double>() < b));
// }
// this monster that I found on stackoverflow will return an error 'a lambda expression with body cannot be converted to expression tree'
// query = query.Where(x => x.ProductAttributes.Any(p =>
// {
// double val = 0;
// if (double.TryParse(p.Value, out val))
// {
// return p.AttrId == item.Key &&
// val >= a &&
// val <= b;
// }
// else
// {
// return false;
// }
// });
}
}
catch(Exception){}
}
Maybe someone alredy faced with this issue and can help me? Here my slider:

Lambda expression execution

When the DoTranslation method is run, the output is the same hash code for
all of the TranslatedObjects. Why is this happening as opposed to having a new List for each TranslatedObject?
public class TranslatedObject
{
public static Expression<Func<OriginalObject, TranslatedObject>> ObjectTranslator = o => new TranslatedObject
{
id = o.id
translatedList = new List<String>()
};
public int id { get; set; }
public List<String> translatedList { get; set; }
}
public class Translator
{
public void DoTranslation()
{
//3 objects returned with ids 1, 2, and 3 respectively
IQueryable<OriginalObject> originalObjects = dataContext.OriginalObjects.Where(o => o.id == 1 || o.id == 2 || o.id == 3);
var translatedObjects = originalObjects.Select(TranslatedObject.ObjectTranslator).ToArray();
foreach(TranslatedObject translated in translatedObjects)
{
Console.WriteLine(translated.translatedList.GetHashCode());
}
}
}
UPDATE: Changed service call to the following linq-to-sql call: dataContext.OriginalObjects.Where(o => o.id == 1 || o.id == 2 || o.id == 3).

Parallel Linq query fails to execute

Hello I have just started to try and understand Parallel Linq and with my first attempt I am having no success. I am using EF 4.0 and a repository pattern class that I created to query the data. I don't believe that the repository pattern is the problem but I could be mistaken.
The database that I have isn't setup the way that I would like it but hey I inherited the system. The code that I am having the problem with is below:
var gId = Sql.ToGuid(Request["ID"]);
var lOrdersGridList = new OrdersGridList(); //Class that only contains properties
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
.Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
grdMain.DataSource = lOrdersForContact.ToDataTable().DefaultView; //ToDataTable extension function converts the List Object to a datatable.
If I run the Code without AsParallel the code executes with no problem however, once I add AsParallel I receive the following error:
Also just in case you wanted to see this is the class that I am declaring as new for the Select Above:
public class OrdersGridList : EntityObject
{
public string ORDER_NUM { get; set; }
public Guid ORDER_ID { get; set; }
public Guid SHIPPING_ACCOUNT_ID { get; set; }
public string SHIPPING_ACCOUNT_NAME { get; set; }
public Guid SHIPPING_CONTACT_ID { get; set; }
public string SHIPPING_CONTACT_NAME { get; set; }
public string NAME { get; set; }
}
If I remove all of the relationships that are used to retrieve data in the select I don't receive any errors:
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
//SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
//SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
//SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
//SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
// .Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
I would be more than happy to give more information if required. Any help you can provide on using PLinq I would appreciate it.
To me it appears like the BaseRepository class creates some kind of LINQ to Entities query using the Find and Select parameters.
Using AsParellel is made for LINQ to Objects, where your code actually evaluates the expressions you pass. Other LINQ dialects, including LINQ to Entities, translate it into a different query language like SQL. The SQL server may do reasonable parallelisation itself.

Categories

Resources