I have the following repro which returns the Name of all MyParent objects with a related MyChild where the specified field (which is only known at runtime) is true.
The sample works, however I am certain there is plenty of room to simplify the query. The test case has much irrelevant data removed such as metadata and navigation properties. The actual data store is an mssql database. Can the group construct be avoided?
using System;
using System.Linq;
using System.Linq.Expressions;
public class MyParent
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
}
public class MyChild
{
public int Id { get; set; }
public bool FieldA { get; set; }
public bool FieldB { get; set; }
public int MyParentId { get; set; }
}
class Program
{
static void Main()
{
var childA = new MyChild { Id = 0, FieldA = false, MyParentId = 0 };
var parentA = new MyParent { Id = 0, Name = "John", Enabled = true };
var childB = new MyChild { Id = 1, FieldA = true, MyParentId = 1 };
var parentB = new MyParent { Id = 1, Name = "Jane", Enabled = true };
var userField = "FieldA";
var parents = new[] { parentA, parentB }.AsQueryable();
var children = new[] { childA, childB }.AsQueryable();
var parameter = Expression.Parameter(typeof(MyChild), "p");
var property = Expression.Property(parameter, userField);
var lambda = Expression.Lambda<Func<MyChild, bool>>(property, parameter);
var query =
from parent in parents
join child in children on parent.Id equals child.MyParentId into grp
from g in grp.AsQueryable().Select(lambda).Where(p => p)
where parent.Enabled
select parent.Name;
}
}
This will do the trick using reflection, no grouping needed.
static void Main()
{
var childA = new MyChild { Id = 0, FieldA = false, MyParentId = 0 };
var parentA = new MyParent { Id = 0, Name = "John", Enabled = true };
var childB = new MyChild { Id = 1, FieldA = true, MyParentId = 1 };
var parentB = new MyParent { Id = 1, Name = "Jane", Enabled = true };
var parents = new[] { parentA, parentB }.AsQueryable();
var children = new[] { childA, childB }.AsQueryable();
var userField = "FieldA";
var childQuery = from child in children.Where(c => c.GetType().GetProperty(userField).GetValue(c).Equals(true)) select child;
var query =
from parent in parents
join child in childQuery on parent.Id equals child.MyParentId
where parent.Enabled
select parent.Name;
}
Related
So here I have some code, which works ok. But I want to change the select part to something else, I am not sure what other methods I can use any help would be appreciated.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People>{new People{Id = 1, Name = "andy1", Age = null}, new People{Id = 2, Name = "andy2", Age = null}, new People{Id = 3, Name = "andy3", Age = null}, new People{Id = 4, Name = "andy4", Age = null}, };
var someOfThem = new List<People>{new People{Id = 1, Name = null, Age = 1}, new People{Id = 2, Name = null, Age = 1},new People{Id = 3, Name = null, Age = 1}};
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
foreach (var item in test)
Console.WriteLine("{0}={1}={2}", item.Id, item.Name, item.Age);
}
}
public class People
{
public int Id
{
get;
set;
}
public int? Age
{
get;
set;
}
public string Name
{
get;
set;
}
}
And here is the result.
1=andy1=1
2=andy2=1
3=andy3=1
I am just wondering is there another way to achieve the same result but a more elegant way? or an easier way?
var test = someOfThem.Select(c =>
{
c.Name = all.Find(a => a.Id == c.Id).Name;
return c;
});
Update
Sorry I did not show my problem properly at first, I have updated my quesiton. Please have a look again.
You can use C#'s LINQ keywords and more specifically, the join keyword assosciated with it:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = from item in someOfThem
join element in all on item.Id equals element.Id
select element;
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
The code version would be
var test = someOfThem.Join(all, item => item.Id, element => element.Id, (item, element) => element);
as shown in Robert's comment
You can use the Join (you can also use a dictionary, but I'm not going to show it):
Here's the syntax for join:
var test = someOfThem.Join(all, item => item.Id, element => element.Id,
(item, element) => new Person {
Id = item.Id ?? element.Id,
Name = item.Name ?? element.Name,
Age = item.Age ?? element.Age
});
You can implement Equals and GetHashCode in your People class and use Intersect.
Or, create an EqualityComparer, that way your comparison logic is decoupled:
class Program
{
public static void Main()
{
var all = new List<People> { new People { Id = 1, Name = "andy1", }, new People { Id = 2, Name = "andy2", }, new People { Id = 3, Name = "andy3", }, new People { Id = 4, Name = "andy4", }, };
var someOfThem = new List<People> { new People { Id = 1, Name = null, }, new People { Id = 2, Name = null, } };
var test = all.Intersect(someOfThem, new PeopleIdComparer()).ToList();
foreach (var item in test)
Console.WriteLine("{0}={1}", item.Id, item.Name);
}
}
public class PeopleIdComparer : IEqualityComparer<People>
{
public bool Equals(People x, People y)
{
return x.Id == y.Id;
}
public int GetHashCode(People obj)
{
return HashCode.Combine(obj.Id);
}
}
public class People
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
Is is possible to have a linq query that populates a class with List for any outer join subqueries?
I've tried various variations of this, but can't get it to work.
Another option would be to populate the class by having more queries, but that would be bad performance wise.
Here's an example, where I try to populate MyClass, using a single query
var result = from p in PersonTable
join cars in CarTable on p.id equals cars.id_person into carsGroup.DefaultIfEmpty()
select new MyClass
{
Person = new Person
{
Id = p.id,
Name = p.name
},
Cars = new List<Car>()
{
Id = carsGroup....??
}
}
public class MyClass
{
public Person Person { get; set; }
public List<PersonCar> Cars { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PersonCar
{
public int Id { get; set; }
pubint int IdPerson {get; set;}
public string Description { get; set; }
}
The LINQ query you have provide is incorrect. The following is a Test that will demonstrate functionality that you're probably looking for:
[TestMethod]
public void TestMethod1()
{
var PersonTable = new List<Person>
{
new Person
{
Id = 1,
Name = "Test1"
},
new Person
{
Id = 2,
Name = "Test2"
},
};
var CarTable = new List<PersonCar>
{
new PersonCar
{
Id = 1,
IdPerson = 2
},
new PersonCar
{
Id = 2,
IdPerson = 3
}
};
var result = (from person in PersonTable
join cars in CarTable on person.Id equals cars.IdPerson into carsGroup
from args in carsGroup.DefaultIfEmpty()
select new MyClass
{
Person = person,
Cars = carsGroup.ToList()
}).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(1, result.Count(res => res.Cars.Count == 0));
Assert.AreEqual(1, result.Count(res => res.Cars.Count == 1));
}
I have the following data:
Parent Id=11
Child Name=A1
Parent Id=22
Child Name=A1
Child Name=B2
Using LINQ, I want all child nodes who have a same name and their parent id like below:
11, A1
22, A1
This is the structure of parent and child nested class that I created:
public class cParent
{
public List<ChildClass> listChildClass;
public string parentId { get; set; }
}
public class ChildClass
{
public string childNAme{ get; set; }
}
You could use grouping to look for duplicates:
var p11 = new cParent { parentId = "11" }; // assuming listChildClass is a collection
p11.listChildClass.Add(new ChildClass { childNAme = "A1" });
var p22 = new cParent { parentId = "22" };
p22.listChildClass.Add(new ChildClass { childNAme = "A1" });
p22.listChildClass.Add(new ChildClass { childNAme = "B2" });
var parents = new List<cParent> { p11, p22 };
var duplicatesByKey =
from parent in parents
from child in parent.listChildClass
let combo = new { parent.parentId, child.childNAme }
group combo by combo.childNAme into g
where g.Count() > 1
select new { g.Key, Values = g.ToList() };
This would work even better if you would implement comparators, but I'll leave that up to the reader.
I have a Linq question: (DotNet Framework 4.0)
I have the following classes:
public class Employee
{
public Guid? EmployeeUUID { get; set; }
public string SSN { get; set; }
}
public class JobTitle
{
public Guid? JobTitleSurrogateKey { get; set; }
public string JobTitleName { get; set; }
}
public class EmployeeToJobTitleMatchLink
{
public EmployeeToJobTitleMatchLink()
{
this.TheJobTitle = new JobTitle() { JobTitleSurrogateKey = Guid.NewGuid(), JobTitleName = "SomeJobTitle:" + Guid.NewGuid().ToString("N") };
}
public Guid LinkSurrogateKey { get; set; }
/* Related Objects */
public Employee TheEmployee { get; set; }
public JobTitle TheJobTitle { get; set; }
}
public class Organization
{
public Organization()
{
this.Links = new List<EmployeeToJobTitleMatchLink>();
}
public int OrganizationSurrogateKey { get; set; }
public ICollection<EmployeeToJobTitleMatchLink> Links { get; set; }
}
In my code below, I can compare 2 child-collections and get the results I need (in "matches1".
Here I am using the "SSN" string property to compare and find the overlaps. And the Console.Write for matches1 works as I expect.
What I don't know how to do is compare the first child collection (org10) to all the children in (allOtherOrgsExceptOrg10 (all the Organizations and all the Links of these Organizations )
The commented out code shows kinda what I'm trying to do, one of my many feeble attempts today.
But basically, match2 would be populated with all the SSN overlaps...but comparing org10 with allOtherOrgsExceptOrg10, all their "Links", and their Employee.SSN's.
org10 overlaps with org20 with "AAA", so match2 would contain "AAA". and org10 overlaps with org30 with "BBB" so match2 would contain "BBB".
Organization org10 = new Organization();
org10.OrganizationSurrogateKey = 10;
Employee e11 = new Employee() { SSN = "AAA", EmployeeUUID = new Guid("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA") };
EmployeeToJobTitleMatchLink link11 = new EmployeeToJobTitleMatchLink();
link11.TheEmployee = e11;
org10.Links.Add(link11);
Employee e12 = new Employee() { SSN = "BBB", EmployeeUUID = new Guid("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB") };
EmployeeToJobTitleMatchLink link12 = new EmployeeToJobTitleMatchLink();
link12.TheEmployee = e12;
org10.Links.Add(link12);
Organization org20 = new Organization();
org20.OrganizationSurrogateKey = 20;
Employee e21 = new Employee() { SSN = "AAA", EmployeeUUID = new Guid("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA") };
EmployeeToJobTitleMatchLink link21 = new EmployeeToJobTitleMatchLink();
link21.TheEmployee = e21;
org20.Links.Add(link21);
Employee e22 = new Employee() { SSN = "CCC", EmployeeUUID = new Guid("CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC") };
EmployeeToJobTitleMatchLink link22 = new EmployeeToJobTitleMatchLink();
link22.TheEmployee = e22;
org20.Links.Add(link22);
Organization org30 = new Organization();
org30.OrganizationSurrogateKey = 30;
Employee e31 = new Employee() { SSN = "BBB", EmployeeUUID = new Guid("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB") };
EmployeeToJobTitleMatchLink link31 = new EmployeeToJobTitleMatchLink();
link31.TheEmployee = e31;
org30.Links.Add(link31);
Employee e32 = new Employee();
e32.SSN = "ZZZ";
EmployeeToJobTitleMatchLink link32 = new EmployeeToJobTitleMatchLink();
link32.TheEmployee = e32;
org30.Links.Add(link32);
IList<Organization> allOtherOrgsExceptOrg10 = new List<Organization>();
/* Note, I did not add org10 here */
allOtherOrgsExceptOrg10.Add(org20);
allOtherOrgsExceptOrg10.Add(org30);
IEnumerable<EmployeeToJobTitleMatchLink> matches1 =
org10.Links.Where(org10Link => org20.Links.Any(org20Link => org20Link.TheEmployee.SSN.Equals(org10Link.TheEmployee.SSN, StringComparison.OrdinalIgnoreCase)));
IEnumerable<EmployeeToJobTitleMatchLink> matches2 = null;
//org10.Links.Where(org10Link => ( allOtherOrgs.Where ( anyOtherOrg => anyOtherOrg.Links.Any(dbSideChild => dbSideChild.TheEmployee.SSN == org10Link.TheEmployee.SSN)) );
if (null != matches1)
{
foreach (EmployeeToJobTitleMatchLink link in matches1)
{
Console.WriteLine(string.Format("matches1, SSN = {0}", link.TheEmployee.SSN));
}
}
if (null != matches2)
{
foreach (EmployeeToJobTitleMatchLink link in matches2)
{
Console.WriteLine(string.Format("matches2, SSN = {0}", link.TheEmployee.SSN));
}
}
matches2 =
allOtherOrgsExceptOrg10.SelectMany(x => x.Links)
.Where(x => org10.Links.Select(o => o.TheEmployee.SSN).Contains(x.TheEmployee.SSN));
You can use the SelectMany on the allOther collection to select all Links over all org's. Then check if any SSN is inside the org10 List.
See: http://msdn.microsoft.com/en-us/library/system.linq.enumerable.selectmany(v=vs.100).aspx
You can use SelectMany to flatten out the collection and then use it just like you have for matches1
IEnumerable<EmployeeToJobTitleMatchLink> matches2 =
org10.Links.Where(
org10Link =>
allOtherOrgsExceptOrg10.SelectMany(allOtherOrgs => allOtherOrgs.Links).Any(
anyOtherLink =>
anyOtherLink.TheEmployee.SSN.Equals(org10Link.TheEmployee.SSN, StringComparison.OrdinalIgnoreCase)));
The SelectMany will make it seem like one IEnumerable instead of and IEnumerable of an IEnumerable.
public class parent
{
public string abc { get; set; }
public childclass pos { get; set; }
}
public class childclass
{
public string value { get; set; }
}
I am currently reading the collection of parent class
var obj =
(from dia in parent
select new
{
abc = dia.abc,
pos = new childclass()
{
value = dia.pos.value
},
})
.ToList();
how do I read the nested class childclass using linq to object , this piece is not working
pos = new childclass()
{
Value = dia.pos.Value
},
Please advise
It works perfectly, I think your problem is case sensitive
Classes:
public class Parent
{
public string Abc { get; set; }
public Childclass Pos { get; set; }
}
public class Childclass
{
public string Value { get; set; }
}
Use:
var parent = new List<Parent> {
new Parent { Abc = "abc1", Pos = new Childclass { Value = "Value1" } },
new Parent { Abc = "abc2", Pos = new Childclass { Value = "Value2" } }
};
var obj =(from dia in parent select new {
abc = dia.Abc,
pos = new Childclass()
{
Value = dia.Pos.Value
},
}).ToList();
C# is case-sensitive. The value field in your class childClass has a lower-case 'v'. Your LINQ statement is referencing it using an upper-case 'V'.
Try the following code:
var obj = (from dia in parent
select new
{
abc = dia.abc,
pos = new childclass()
{
value = dia.pos.value // 'Value' has been changed to 'value'
},
}).ToList();
Here is a test I ran to verify that the updated LINQ statement works:
var parents = new List<parent>()
{
new parent{abc = "abc", pos = new childclass{ value = "value" }}
};
var obj =
(from dia in parents // changed to 'parents' to match my variable above
select new
{
abc = dia.abc,
pos = new childclass()
{
value = dia.pos.value
},
}).ToList();
foreach (var par in obj)
{
Console.WriteLine(par);
}
The resulting output was:
"{ abc = abc, pos = ProgrammingTestBed.Program+childclass }"