With a collection of Rules I am trying to create another collection of Rules ignoring the Site property and creating a unique list.
public class Rule
{
public int TestId { get; set; }
public string File { get; set; }
public string Site { get; set; }
public string[] Columns { get; set; }
}
So if my collection had values like below:
var rules = new List<Rule>
{
new Rule { TestId = 1, File = "Foo", Site = "SiteA", Columns = new string[] { "ColA", "ColB" }},
new Rule { TestId = 1, File = "Foo", Site = "SiteB", Columns = new string[] { "ColA", "ColB" }}
};
I am wanting the end result
var uniqueRules = new List<Rule>
{
new Rule { TestId = 1, File = "Foo", Site = null, Columns = new string[] { "ColA", "ColB" }}
};
Having tried various combinations like below I'm still getting 2 results back, how do I achieve the expected result?
var uniqueRules = rules
.GroupBy(r => new { r.TestId, r.File, r.Columns })
.Select(g => g.Key)
.Distinct()
.ToList();
The problem is that a string[] has not overridden Equals and GetHashCode, that's why just the references are compared at r.Columns. You need to provide a custom IEqualityComparer<T>:
public class RuleComparer : IEqualityComparer<Rule>
{
public bool Equals(Rule x, Rule y)
{
if (object.ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if(!(x.TestId == y.TestId && x.File == y.File)) return false;
return x.Columns.SequenceEqual(y.Columns);
}
// from: https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
public int GetHashCode(Rule obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.TestId.GetHashCode();
hash = hash * 23 + (obj.File?.GetHashCode() ?? 0);
foreach(string s in obj.Columns)
hash = hash * 23 + (s?.GetHashCode() ?? 0);
return hash;
}
}
}
Now the LINQ query becomes trivial:
List<Rule> uniqueRules = rules.Distinct(new RuleComparer()).ToList();
There are several observations to be made here:
GroupBy() will have the same effect as doing Distinct(). So either create an EqualityComparer that will perform the comparison for you, or just do GroupBy(), no need to do both.
You're getting the Key after the grouping. You probably want the entire object back, so use .First() if you want an actual Rule, and don't care which one it is if multiple ones are in the same grouping.
The rules are distinct because the Columns are references to different arrays, which are not compared by value but by reference.
To combine all these observations, you could use the following code if you don't want to write a custom EqualityComparer and go the grouping way:
var uniqueRules = rules
.GroupBy(r => new { r.TestId, r.File, Columns = string.Join(",", r.Columns) })
.Select(r => r.First())
.ToList();
This will simply use a string for the columns, making it a property that is also compared by value.
Note that this is only possible due to the fact that Columns is a simple array of strings. For more complex types this can't be done as conveniently.
I would recommend to extend your class Rule, to implement equals method as below:
public class Rule :IEquatable<Rule>
{
public int TestId { get; set; }
public string File { get; set; }
public string Site { get; set; }
public string[] Columns { get; set; }
public bool Equals(Rule other)
{
return TestId == other.TestId &&
string.Equals(File, other.File) &&
Equals(Columns, other.Columns);
}
}
As you see we ignore the Site field when comparing the two classes. This also gives you the flexibility of altering your comparison in future.
And then use : rules.Distinct();
The problem is that although Columns both look alike new string[] { "ColA", "ColB" } but the are not referencing the same object and they only have equal data. try this:
string[] cols = new string[] { "ColA", "ColB" };
var rules = new List<Rule>
{
new Rule { TestId = 1, File = "Foo", Site = "SiteA", Columns = cols},
new Rule { TestId = 1, File = "Foo", Site = "SiteB", Columns = cols}
};
Now your own query should work correctly:
var uniqueRules = rules
.GroupBy(r => new { r.TestId, r.File, r.Columns })
.Select(g => g.Key)
.Distinct()
.ToList();
Related
public class UserValues
{
public string UserId { get; set; }
public int FieldId { get; set; }
public string FieldValue { get; set; }
}
public class LookupMeta
{
public int FieldId { get; set; }
public int Id { get; set; }
public int FieldValueId { get; set; }
public string Title { get; set; }
}
I have kept this in 2 different lists after reading it from DB.
Now I want to compare both the list with
FieldId == FieldId
and FieldValue equals Id
then replace FieldValue from uservalues to FieldValueId from lookupMeta
UserValues
.Where(x => LookupMeta.Any(y =>
y.FieldId == x.FieldId &&
y.FieldValueId.Equals(x.FieldValue)))
.Select(x => x.FieldValue.Replace(x.FieldValue, ???))
I am looking at this link as well. I am struck C# LINQ code for two list compare and replace
Is it good to have in List and doing like this or is there any other optimized way?
Based on the comment that has been left on pwilcox's answer it seems like the OP is look for a solution where the unmatched rows are also included. That means instead of using inner join we are looking for a left outer join.
In the world of Linq this could be achieved via a combination of GroupJoin, SelectMany and Select operators.
In order to be able to join on two different columns we have to introduce an intermediate class to be able to tell the types of the GroupJoin. So, I have created the following class:
internal class IntermediateKey
{
public int Id { get; set; }
public string Value { get; set; }
}
We also have to define a comparer for this class to be able to find matching data:
internal class IntermediateKeyComparer : IEqualityComparer<IntermediateKey>
{
public bool Equals(IntermediateKey x, IntermediateKey y)
{
return x.Id == y.Id && x.Value == y.Value;
}
public int GetHashCode(IntermediateKey obj)
{
return obj.Id.GetHashCode() + obj.Value.GetHashCode();
}
}
Please bear in mind that this implementation is quite simplified. The correct way to implement it is shown in this thread.
Now can define our query as this:
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(paired, meta) => new { Value = paired.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
We defined that userValues should be left outer joined on lookupMetas
if uv's FieldId is matches to lm's FieldId
and if uv's FieldValue is matches to lm's Id's string representation
With the SelectMany we choose either the matching LookupMeta entity or null
With the Select we update the UserValue's FieldValue property only if there is a related LookupMeta otherwise we use its original value.
Now let's see how this works with some sample data:
static void Main(string[] args)
{
var userValues = new List<UserValue>
{
new UserValue { FieldId = 1, FieldValue = "2"},
new UserValue { FieldId = 2, FieldValue = "3"},
new UserValue { FieldId = 4, FieldValue = "5"}
};
var lookupMetas = new List<LookupMeta>
{
new LookupMeta { FieldId = 1, Id = 2, FieldValueId = 20 },
new LookupMeta { FieldId = 2, Id = 3, FieldValueId = 30 },
new LookupMeta { FieldId = 3, Id = 4, FieldValueId = 40 },
};
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(x, meta) => new { Value = x.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
foreach (var maybeUpdatedUserValue in result)
{
Console.WriteLine($"{maybeUpdatedUserValue.FieldId}: {maybeUpdatedUserValue.FieldValue}");
}
}
The output will be:
1: 20
2: 30
4: 5
So, as you can see there is no matching LookupMeta for the last UserValue that's why its FieldValue remained intact.
If I follow you correctly, then the .Join() method in LINQ may be of use to you. Here I use it to accomplish what I think you're after.
UserValues
.Join(
LookupMeta,
uv => new { uv.FieldId, uv.FieldValue },
lm => new { lm.FieldId, lm.FieldValueId },
(uv,lm) => {
uv.FieldValue = lm.FieldValueId;
return uv;
}
);
The second and third lines in the method build anonymous objects from the source tables. The values of these are matched to make a link.
The last line takes the joined entries as inputs and then gives your output. In your case, I just return the UserValues entry. But before I do I change its "FieldValue" property to the "FieldValueId" property of the LookupMeta entry.
You have some inconsistencies. For instance, you talk about matching FieldValue to Id in the paragraph, but in the code you match FieldValue to FieldValueId. Also, you use == in one comparison and .Equals() in the other. No wrong answer here. I just don't know your underlying objects. So you may have to modify my code a bit to get what you want. But it shows the general strategy that I hope will work for you.
I have a list of class Products:
class Products
{
public string Name { get; set; }
public string Size { get; set; }
public string ProductId { get; set; }
public string Category { get; set; }
}
I would like to use one TextBox to search through any matching products utilizing a wildcard value. This would return me a list of items where all values in the search string are found somewhere in the four properties listed above.
As of now, I'm using string[] values = searchText.Split("*".ToCharArray) to seperate the values of the search string into an array of strings (based on an asterisk wildcard). From there, I get stumped, since I want to search for all values of the search string in all properties of the class.
I tried to figure it out using a complex LINQ statement, but I have not been able to figure it out how to make this work. I don't know how to build a Where statement when I don't know how many values I'm going need to test against my four properties.
So, if you're breaking search up into separate keywords, using * as the delimiter, which you've described in the comments, then this is how you do it:
var products = new List<Products>()
{
new Products()
{
Name = "theo frederick smith",
Size = "",
ProductId = "",
Category = "brown",
}
};
var searchText = "fred*brown";
var splits = searchText.Split("*".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var results =
products
.Where(p => splits.All(s =>
p.Name.Contains(s)
|| p.Size.Contains(s)
|| p.ProductId.Contains(s)
|| p.Category.Contains(s)));
That matches the input.
Alternatively, if you really want a wildcard search, such as "fred*smith" (meaning that any one field must contain "fred" followed by zero or more characters and followed by "smith"), then this works:
var products = new List<Products>()
{
new Products()
{
Name = "theo frederick smith",
Size = "",
ProductId = "",
Category = "brown",
}
};
var searchText = "fred*smith";
var wildcard =
new Regex(
String.Join(".*",
searchText
.Split('*')
.Select(x => Regex.Escape(x))));
var results =
products
.Where(p => new []
{
p.Name, p.Size, p.ProductId, p.Category
}.Any(x => wildcard.IsMatch(x)));
Naively, you could write
products.Where(x=>x.Name.Contains(search)
|| x.Size.Contains(search)
|| x.ProductId.Contains(search)
|| x.Category.Contains(search))
You would be better off putting that logic in your Product class.
So you would have:
class Products
{
public bool Contains(string term) {
return Name.Contains(search) || Size.Contains(search) ||
ProductId.Contains(search) || Category.Contains(search)
}
public string Name { get; set; }
public string Size { get; set; }
public string ProductId { get; set; }
public string Category { get; set; }
}
And then simply products.Where(x=>x.Contains(search))
You could also use reflection to get all the property names and do a for each on each string and check for Contains.
EDIT
The complete scenario is, I need to retrieve a list of objects (in this case is Person) and also apply a filter over the collection returned on the property Files, in short terms is I want to get only the persons who has more than one file of a specific type, but also I want to retrieve only this specific file from this query.
Object representation:
public class Person(){
public string name {get; set;}
public int id {get; set;}
public List<File> file {get; set;}
}
And file entity:
public class File(){
public string name {get; set;}
public int id {get; set;}
public int extension {get; set;}
public int type_file {get; set;}
}
OBS: i use EF6 and .net framework.
Example:
//returning a IQueryable
var t = service.getAll<person>()
.Where(x => x.id == id)
.Where(y => y.files.Any(l => l.type_file == 1))
.FirstOrDefault();
Since you didn't provide a Minimal, Complete and Verifiable Example, I've created a simple representation of your scenario:
static List<Person> GetAllPersons()
{
var ret = new List<Person>(new []
{
new Person(){ Id = 10 },
new Person(){ Id = 4 },
new Person(){ Id = 8 }
});
var rnd = new Random();
foreach(var person in ret)
for(int i = 0; i <3; i++)
person.Files.Add(new File() { FileType = rnd.Next(1,3) , Name= "File " + (i + 1).ToString() });
return ret;
}
class Person
{
public int Id { get; set; }
public List<File> Files { get;set; }
public Person()
{
Files = new List<File>();
}
}
class File
{
public int FileType { get; set; }
public string Name { get; set; }
}
If I understood your need well, you want to change the Files property of the first Person that matches to your restrictions.
It would be something like this:
public static void Main()
{
var Id = 4;
var all = GetAllPersons();
var t = all
.Where(x => x.Id == Id)
.Select(P => new Person()
{
Id = P.Id,
Files = P.Files.Where(l => l.FileType == 1).ToList()
})
.Where(y => y.Files.Count > 0)
.FirstOrDefault();
}
This complete example is available on dotnetfiddle
But for sure, such kind of logic makes no sense and it's wrong in all possible ways. I'm pretty confident that the problem you're facing is being raised from your confusing logic and it's getting even wrost.
I suggest you to organize your logic before going thru this dangerous way.
You are using FirstOrDefault, which will return the first object found by an enumerable (or null, if the list is empty*). If you want the list, remove this method call and add ToList instead.
var t = service.getAll<person>()
.Where(x => x.id == id)
.Where(y => y.files.Any(l => l.type_file == 1))
.ToList();
*The more precise answer is that it will return the default value of whatever type the IEnumerable represents. For value types, this will be the default value of that type (e.g. 0 for int, false for bool, etc). For reference types, this will be null.
This is my solution this problem, i thank you for all you help.
//var person is DTO
var person= base.Get(id);
var list = new List<FileDTO>();
person.file.ForEach(delegate (FileDTO obj)
{
if (obj.type_file== (int)TypeFile.Attachment)
{
obj.archive = null;
list.Add(obj);
}
});
person.file = list;
return person;
Please refer to ff. codes:
MainObj:
public class MainObj {
public string Name { get; set; }
public int SomeProperty { get; set; }
public List<SubObj> Subojects { get; set; }
}
SubObj:
public class SubObj {
public string Description { get; set; }
public decimal Value { get; set; }
}
QUESTION: I have a List<MainObj>. Each item has equal number of SubObj. How do I sum the values of each SubObj from MainObj A which corresponds to the same index as the SubObj in the other MainObj in the list.
To illustrate further:
Results:
{
Name: "something....",
Value: SUM(MainObj_A.SubObj[0], MainObj_B.SubObj[0] .... MainObj_n.SubObj[0]
},
{
Name: "don't really care...."
Value: SUM(MainObj_A.SubObj[1], MainObj_B.SubObj[1] .... MainObj_n.SubObj[1]
},
{
Name: "don't really care...."
Value: SUM(MainObj_A.SubObj[2], MainObj_B.SubObj[2] .... MainObj_n.SubObj[2]
}
.... so on and so forth
I know I can loop thru each item in MainObj then perform the sum but I was hoping that there is a simpler way using Linq.
Sorry if I can't put the right words for my question. I hope the illustrations helped.
Every help would be much appreciated. CHEERS!
As I understand the question you look for something like this:
var results = list.Select((m, i) => new
{
m.Name,
Value = list.Sum(t => t.SubObj[i].Value)
}).ToList();
This creates a list of an anonymous type containing the name of each main object and the sum of all subobject values with the same index as the main object.
It's not clear from your question, but if you want to sum all subojects (not only as many as there are main objects), you can do it like this:
var results = Enumerable.Range(0, list[0].SubObj.Count)
.Select(i => list.Sum(m => m.SubObj[i].Value)).ToList();
This gives you a list of the sums (without names, as your examples suggest that you "don't really care" about any names).
For flexibility and slightly boosted performance, I'd prefer a mixed LINQ-sql way as shown in my answer below.
var result = from x in list
where x...
group x by x.SomeProperty
into item
select new {
Id = item.Key,
Name = item.Name,
Value = item.SubObj.Sum(i => i.Value)
};
I would extend solution of Rene, to sum over all subitems:
var result = list.First().Subojects.Select((m, i) => new
{
Name = i.ToString(),
Value = list.Sum(t => t.Subojects[i].Value)
}).ToList();
i have used it with this values:
var list = new List<MainObj>
{
new MainObj { Name = "A", Subojects = new List<SubObj>{new SubObj{ Value = 1}, new SubObj{ Value = 2}, new SubObj{ Value = 3}}},
new MainObj { Name = "B", Subojects = new List<SubObj>{new SubObj{ Value = 1}, new SubObj{ Value = 2}, new SubObj{ Value = 3}}}
};
And result is:
0:2
1:4
2:6
Consider the following classes:
public class Recipe
{
public string Id { get; set; }
public ICollection<RecipeFacet> RecipeFacets { get; set; }
}
public class RecipeFacet
{
public string Id { get; set; }
public Facet Facet { get; set; }
public string RecipeId { get; set; }
}
public class Facet
{
public string Name { get; set; }
}
I need to improve an existing query. I was thinking of using Linq's deferred execution. How would I write a Linq query that returns only Recipes, that contains ALL Facets I specify in a list of Tuples?
This is the original code that loops through Recipes and its Facets. It works but it is slow if my intial results query has lots of Recipes.
IQueryable<Recipe> result; //assume we have data here
string query = "Cuisine:American+Recipe-Type:dinners";
IEnumerable<Tuple<string, string>> taxFacets = query
.Split(' ')
.Select(tf => tf.Split(':'))
.Select(tf => new Tuple<string, string>(tf[0], tf[1]))
.Distinct();
var recipeFacetCollection = result.Select(r => r.RecipeFacets).ToList();
var matchedRecipesIds = new List<string>();
var recIds = result.Select(r => r.Id).ToList();
// initially, include all recipes
matchedRecipesIds.AddRange(recIds);
// loop through each recipe's facet collection
foreach (var col in recipeFacetCollection)
{
// loop through the tax facets from the query
foreach (var tf in taxFacets)
{
var exists = col.Any(f => f.Facet.Name.Equals(tf.Item2, StringComparison.OrdinalIgnoreCase));
// remove any recipe that is missing a facet
if (!exists)
{
matchedRecipesIds.Remove(col.First().RecipeId);
}
}
}
result = result.Where(r => matchedRecipesIds.Contains(r.Id));
How can I have a nice Linq query with deferred execution?
UPDATE::
Turning my Tuple into a List allows me to do this. But this query doesn't return any of my records.
This is my criteria:
Recipes, that have a collection of RecipeFacts, that contains Facets that have Name = "American" AND Name = "dinners".
var listFacets = new List<string>()
{
"American",
"dinners"
};
result = result
.Where(r => r.RecipeFacets
.All(f => !listFacets.Any(t => t.Equals(f.Facet.Name, StringComparison.OrdinalIgnoreCase))));
Your query logic selects all recipes whose facets don't exist in listFacets.
#Hung's logic is closer but selects recipes that have all of their facets in listFacets
I think that you want to select all recipes that contain all listFacets.
Simplifying the example to use lists of strings:
var listFacets = new[] { "a", "d" };
var recipes = new[] { new[] { "a" },
new[] { "a", "d" },
new[] { "a", "d", "e" },
new[] { "x" }
};
// correct query, returns 2 results ad and ade
var result = recipes.Where(r => listFacets.All(f => r.Any(rf => rf == f)));
// original incorrect query, returns x
var result2 = recipes.Where(r => r.All(f => !listFacets.Any(rf => rf == f)));
I am not quite sure cause your code block is quite long but here is what I can come up with
result = result.Where(r => r.RecipeFacets.All(f => taxFacets.Any(t => t.Item1.Equals(f.Facet.Name))));
Let me know if it helps or not
Remove the exclamation from the clause in the check on the listFacets collection.
result = result.Where(r => r.RecipeFacets.All(f => listFacets.Any(t => t.Equals(f.Facet.Name, StringComparison
I got this from #Rimp's help.
WHERE - filter
ALL - Require that All values from listFacets
ANY - is in ANY of the Facets.
result = result
.Where(x => listFacets
.All(lf => x.RecipeFacets
.Any(f => f.Facet.Slug.Equals(lf, StringComparison.OrdinalIgnoreCase))));