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.
Related
In Parts class we have Data dictionary that contains key "Number" and value "1" for example. The key is always called "Number" and the value is always string of some number 1,2,3 etc. I want to assign to one variable (List) all items that has the key "number" with their values and then to group them by the id in Parts. So in the end the result should be the Id from Parts, Number and its value.
public class People
{
public List<Parts> Parts { get; set; }
}
public class Parts
{
public string Name {get;set;}
public string Id {get;set;}
public Dictionary<string,string> Data {get;set}
}
var msf = new People();
Currently my example that does not work properly with linq :
var temp = msf
.Parts
.Select(s => s.Data.Keys.Where(key => key.Contains("Number"))
.ToList()
.Select(s = > s.Value));
Can someone give me better solution for this scenario code with linq?
"People":[
"id":"1234567"
"Parts":[
"id":"234567",
"name":"Lqlq"
"Data":{
"number" : "1"
}
"id":"3424242",
"name":"Lqlq2"
"Data":{
"number" : "2"
}
]
]
This should give you a Dictionary<string, List<string>> containing a list of ID strings for each "Number" value:
var idsByNumber = msf.Parts.Where(p => p.Data.ContainsKey("number")) // filter for all that have a number
.Select(p => new { ID = p.ID, Number = p.Data["number"] }) // select ID and the number value
.GroupBy(x => x.Number) // group by number
.ToDictionary(g => g.Key, g => g.ToList()); // create dictionary number -> id list
Here's an alternative syntax.
var temp = from part in msf.Parts
where part.Data["Number"] == "2"
select part;
Usually is a good idea to ask your questions using an MCVE - here's some code that can be pasted in Linqpad:
void Main()
{
var msf = new People() {
Parts = new List<Parts> {
new Parts { Name = "Lqlq", Id = "234567", Data = new Dictionary<string, string> { { "Number", "1"} } },
new Parts { Name = "Lqlq2", Id = "3424242", Data = new Dictionary<string, string> { { "Number", "2"} } },
}
};
var temp = from part in msf.Parts
where part.Data["Number"] == "2"
select part
;
temp.Dump();
}
public class People
{
public List<Parts> Parts { get; set; }
}
public class Parts
{
public string Name { get; set; }
public string Id { get; set; }
public Dictionary<string, string> Data { get; set; }
}
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();
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
I have two lists as below:
var processedOrders = this._requestReviewRecordService.GetAll().ToList();
var orders = _orderRepository.Table.Where(o => o.OrderStatusId == (int)OrderStatus.Complete && o.CreatedOnUtc < EntityFunctions.AddMinutes(DateTime.Now, -minutes)).ToList();
The lists are of different types, but both contain a property called OrderId.
Essentially I want to filter the second list "orders" of any records that a matching OrderId.
I've tried Linq's Except method, but this seems to only play nicely with primitive types.
Can anyone point me in the right direction - I didnt think this would be quite so challenging!
Thanks in advance
Al
Here is a sample that does what you're after:
public class TypeOne
{
public int OrderId { get; set; }
public string SomeOtherField { get; set; }
}
public class TypeTwo
{
public int OrderId { get; set; }
public string MainField { get; set; }
}
class Program
{
static void Main(string[] args)
{
// A little bit of setup
var first = new List<TypeOne>() { new TypeOne { OrderId = 1, SomeOtherField = "One" }, new TypeOne { OrderId = 2, SomeOtherField = "Two" } } ;
var second = new List<TypeTwo>() { new TypeTwo { OrderId = 1, MainField = "One" }, new TypeTwo { OrderId = 2, MainField = "Two" }, new TypeTwo { OrderId = 3, MainField = "Buckle" }, new TypeTwo { OrderId = 4, MainField = "MyShoe" } };
// Here's where we do the interesting bit
var firstIds = from id in first
select id.OrderId;
var query = from item in second
where firstIds.Contains(item.OrderId)
select item;
// And some boring results
foreach (var i in query)
{
Console.WriteLine(string.Format("[OrderId: {0}, MainField: {1}]", i.OrderId, i.MainField));
}
Console.ReadLine();
}
Keep in mind that this example uses simple lists as the stores for the objects. This approach doesn't always work if you're using an Entitiy Framework EntitySet as the source collection in any of your queries, because they haven't implemented an extension method for Contains<>() that works with EF. That being said, you could materialise your queries in to lists first, and then this approach would work.
I'm struggling with RavenDB's multi map/reduce concept and recently asked this question regarding how to properly write a multi map/reduce index.
I got the simple index in that question working but when I tried to make it a bit more complicated, I cannot make it work. What I want to do is to have the result of the index to contain a list of string, i.e:
class RootDocument {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
}
public class ChildDocument {
public string Id { get; set; }
public string RootId { get; set; }
public int Value { get; set; }
}
class RootsByIdIndex: AbstractMultiMapIndexCreationTask<RootsByIdIndex.Result> {
public class Result {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
public int Value { get; set; }
}
public RootsByIdIndex() {
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = default(IList<string>),
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.Select(x => x.Items).Where(
x => x != default(IList<string>).FirstOrDefault(),
Value = g.Sum(x => x.Value)
};
}
}
Basically I tried to set the Items property to default(IList) when mapping the ChildDocuments and to value of the RootDocument's Items property. This doesn't work, however. It gives the error message
Error on request Could not understand query:
-- line 2 col 285: invalid Expr
-- line 2 col 324: Can't parse double .0.0
when uploading the index. How do I handle lists in multi map/reduce indexes?
Don't use List in your indexes, instead, use arrays.
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = new string[0],
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.SelectMany(x=>x.Items),
Value = g.Sum(x => x.Value)
};
David, you need to understand that RavenDB stores its indexes with Lucene.NET. That means, you cannot have any complex .net type inside your index.
In your example, I suggest you use a simple string instead of IList<string>. You can then join your string-items:
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = (string)null,
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = string.Join(";", root.Items),
Value = 0
});