I am trying to modify a LINQ query to select some properties into an array but am struggling to achieve part of it.
toRun.AddRange(entity.Properties
.Select(property => property.PrimaryField)
.Select(field => new { field, entity = entity.EntityName, table = field.Table, key = entity.EntityIdField })
I need this amending so that if a second property called SecondaryField is not null or empty string it will be added to the results of the first Select statement.
For example if entity.Properties contains:
Property { PrimaryField = "a", SecondaryField = "b" },
Property { PrimaryField = "c", SecondaryField = "" }
I would like the first Select statement to return:
{ "a", "b", "c" }
Appreciate any help thanks.
This seems to reproduce what you want: you have a class with two properties:
public class Foo
{
public string Bar { get; set; }
public string Baz { get; set; }
}
Of which you have a collection:
var foos = new List<Foo>
{
new Foo { Bar = "a", Baz = "b" },
new Foo { Bar = "c", Baz = "" },
};
And from this collection, you want to select all properties that have a non-empty value into an array.
You can do so using SelectMany():
var result = foos.SelectMany(f => new[] { f.Bar, f.Baz })
.Where(p => !string.IsNullOrWhiteSpace(p))
.ToArray();
You select a new array containing the value of both properties, then filter out the values you don't want, and turn the result into an array again.
This should be pretty simple - get both fields, use a Where to remove the null/empties and turn to an array:
var result = entity.Properties.SelectMany(p =>new[]{ p.PrimaryField,p.SecondaryField})
.Where(x => !String.IsNullOrEmpty(x))
.ToArray();
Live example: http://rextester.com/MHM61977
Related
The Model has properties Id, Code etc.
I want to create 4 data with specific different codes.
var data = _fixture.Build<MyModel>()
.With(f => f.Code, "A")
.CreateMany(4);
This results in all 4 data with Code "A". I want the 4 data to have codes "A", "B", "C", "D"
Thanks in Advance
There is an easier way to do this
string[] alphabets = { "A", "B", "C", "D" };
var queue = new Queue<string>(alphabets);
var data = _fixture.Build<MyModel>()
.With(f => f.Code, () => queue.Dequeue())
.CreateMany(alphabets.Length);
Output
[
{Code: "A"},
{Code: "B"},
{Code: "C"},
{Code: "D"}
]
In case if you want the result in reverse order use Stack instead of Queue and Pop instead of Dequeue
Assuming you only need 4 items you can define your collection of codes and use it to generate the 4 models using LINQ.
public static object[][] Codes =
{
new object[] { new[] { "A", "B", "C", "D" } }
};
[Theory]
[MemberAutoData(nameof(Codes))]
public void Foo(string[] codes, Fixture fixture)
{
var builder = fixture.Build<MyModel>(); // Do any other customizations here
var models = codes.Select(x => builder.With(x => x.Code, x).Create());
var acutal = models.Select(x => x.Code).ToArray();
Assert.Equal(codes, acutal);
}
public class MyModel
{
public int Id { get; set; }
public string Code { get; set; }
}
This seems ripe for an extension method:
public static IPostprocessComposer<T> WithValues<T, TProperty>(this IPostprocessComposer<T> composer,
Expression<Func<T, TProperty>> propertyPicker,
params TProperty[] values)
{
var queue = new Queue<TProperty>(values);
return composer.With(propertyPicker, () => queue.Dequeue());
}
// Usage:
var data = _fixture.Build<MyModel>()
.WithValues(f => f.Code, "A", "B", "C", "D")
.CreateMany(4);
I have a list contains another list where I supposed to get the common elements.
Class model:
PlanInfo has shiftName, ilist of Plan.
Plan has start time, end time
public class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
public class PlanInfo
{
public string ShiftName { get; set; }
public IList<Plan> lstPlan { get; set; }
}
iList of PlanInfo contains
[“shift1”, [1000,1200]],
[“shift2”,[1000,1100]],
[“shift3”,[1000,1200]]
Expected output in this should be empty since 1000,1200 doesn’t exist in shift2
[“shift1”, [1000,1200]],
[“shift2”,[[1000,1200],[1000,1100]],
[“shift3”,[1000,1200]]
Should return [1000,1200] since it’s common in all lists.
I tried using intersect, but here IList<PlanInfo is not fixed length. it could have more than one records.
Kindly suggest me which LINQ query serve the above result
Hmm, If I understand the requirements: Given a list of PlanInfo, find any Plans common to all PlanInfo...
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.ShiftName, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
This assumes that a Plan can only be counted once within a PlanInfo. (No duplicate plans) This also assumes that the plan info references for the same start/end times are pointing to the same object instance. If not, then you cannot group on the Plan, you will need a unique key (Like a plan ID) to group on. If these are EF entities pulled from a DbContext then they will be the same reference.
First get the total # of plan infos. In your example this would return 3.
Next, for all plan infos, use SelectMany to fetch the Plans, but compose that down into the PlanInfo.ShiftName + the Plan. This flattens your one to many. Next group by the Plan so that we can count the # of PlanInfos that each Plan appears in. Any/all counts that match the total number of PlanInfos means a Plan that appears in all PlanInfos, Select the Key to get that grouped Plan(s) and that should have it.
Edit: adding an example...
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2 = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
private class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
private class PlanInfo
{
public string Name { get; set; }
public List<Plan> Plans { get; set; }
}
That was the test I had run using these stub classes. In this case the test will return back 1 match, for the Plan 2 value.
To outline the issue with ensuring plan references for the same start/end times match: If the setup looked like this:
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2A}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2B, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2B}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
In this case even though plan 2A and 2B have the same start/end time, the group by would not group them together because they represent 2 references to 2 objects. This though would be fine:
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = plan2A;
Both point to the same reference. If you do have different references for the same plan ranges, you would need a planID then group on a PlanId. Ideally though I would check why the references don't match because they should to avoid potential errors based on assumptions of equality.
One can use Aggregate with Intersect on PlanInfo.Plans like:
var plansCommon = planInfoList.Select(p => p.Plans)
.Aggregate<IEnumerable<Plan>>((p1, p2) =>
p1.Intersect(p2, new PlanComparer()))
.ToList();
// Implement IEqualityComparer
class PlanComparer : IEqualityComparer<Plan>
{
public bool Equals(Plan x, Plan y)
{
if (x.Start == y.Start &&
x.End == y.End)
return true;
return false;
}
public int GetHashCode(Plan obj)
{
return obj.Start.GetHashCode() ^ obj.End.GetHashCode();
}
}
The Intersect will recursively apply on Plans list of each PlanInfo to provide list of Plan common across all.
I am using a list collection with two properties Name and Group.
I need to create a list collection which it will contain only elements from first group, then another list with elements from the second group and finally the third group.
My code:
public class Person
{
public string Name { get; set; }
public string Group { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
var mItems = new List<Person>();
mItems.Add(new Person{ Name="A", Group="1", });
mItems.Add(new Person { Name = "B", Group = "1", });
mItems.Add(new Person { Name = "C", Group = "2", });
mItems.Add(new Person { Name = "D", Group = "2", });
mItems.Add(new Person { Name = "E", Group = "3", });
mItems.Add(new Person { Name = "F", Group = "3", });
mItems.Add(new Person { Name = "G", Group = "1", });
}
I need to create a list with elements which are on the first group, example group "1".( i notice that this is an example, i don't not know the exactly group name in my application, it can be anything)
So my expected result is a list with A,B and G, then a second list with C,D and finally a third list with E,F. I have found code below:
var results = mItems.GroupBy(x => x.Group)
.Select(g => g.OrderBy(x => x.Group).FirstOrDefault());
But i am taking the exactly oposite result. I am taking only the first element of each group.
You need to place your FirstOrDefault() call after your Select() as it's currently inside of the select, which is causing the first of each group to be selected:
var results = mItems.GroupBy(x => x.Group)
.Select(g => g.OrderBy(x => x.Group))
.FirstOrDefault();
Likewise, if you wanted to output every group, you could just remove the FirstOrDefault() call altogether. This would return a collection of ordered collections, which you could access individually via the ElementAt() function:
var results = mItems.GroupBy(x => x.Group)
.Select(g => g.OrderBy(x => x.Group))
.ElementAt(someIndex);
Example
You can see a working example here.
Basically I'm looking to select both string columns and put it all into a single array of strings. Right now I'm having to do two selects and combine the results. It isn't a huge deal, I just think it looks awkward. Any suggestions on how to accomplish the same goal with one linq statement? Here is a test case I'm using to mess around:
[TestFixture]
public class test {
public class Values {
public string Present { get; set; }
public string Previous { get; set; }
public bool Flag { get; set; }
}
[Test]
public void test1() {
var list = new List<Values> {
new Values { Present = "present1", Previous = "previous1", Flag = false },
new Values { Present = "present2", Previous = "previous2", Flag = false },
new Values { Present = "present3", Previous = "previous3", Flag = true },
new Values { Present = "present4", Previous = "previous4", Flag = true }
};
var r1 = list.Where(c => c.Flag).Select(c => c.Present);
var r2 = list.Where(c => c.Flag).Select(c => c.Previous);
var combined = r1.Concat(r2);
Assert.AreEqual(4, combined.Count());
}
}
An alternative solution with using SelectMany (it keeps the duplicates):
var combined = list.Where(c => c.Flag)
.SelectMany(c => new[] { c.Present, c.Previous });
Assert.AreEqual(4, combined.Count());
Does the final ordering of the strings in the list matter? If not, it seems like this would be the clearest way to accomplish this:
var strings = new List<String>();
foreach (var value in list.Where(c => c.Flag))
{
strings.Add(value.Present);
strings.Add(value.Previous);
}
I want know to make a query using linq, between a collection of objects
and a collection of values. In the sample code below, I make the question about it.
class USER
{
public string Name { get; set; }
}
public class MyClass
{
public MyClass()
{
List<USER> listUser = new List<USER>();
listUser.Add(new USER { Name = "A" });
listUser.Add(new USER { Name = "B" });
listUser.Add(new USER { Name = "C" });
listUser.Add(new USER { Name = "D" });
string[] arrayNames = { "A", "B" };
}
}
Using Linq how can I get all USER in listUser with them Name equals to the arrayNames values.?
The expected results wold be
//listUser[0] --> User with Name == "A"
//listUser[1] --> User with Name == "B"
Thanks in advance.
HashSet<string> names = new HashSet<string>(new string[]{ "A", "B" });
var selectedUsers = listUser.Where(user => names.Contains(user.Name));
The hashset is optional and overkill if you have only a few users but it guarantees optimal lookup performance if you have a lot of users.
listUser.Where(u => arrayNames.Contains(u.Name)).ToList();
This should so what you want, tried it in a console app. Worked a treat
var query = listUser.Where(i => arrayNames.Contains(i.Name));
foreach (var item in query)
{
Console.WriteLine(item.Name);
}
Console.ReadKey();
The key part is the arrayNames.Contains(i.Name) as this is inverse to intuition in that you specify the IEnumerable and then the linq parameter inside the contains method.
listUser = (from u in listUser where arrayNames.Contains(u.Name) select u).ToList();