How to make linq master detail query for 0..n relationship? - c#

Given a classic DB structure of Orders has zero or more OrderLines and OrderLine has exactly one Product, how do I write a LINQ query to express this?
The output would be
OrderNumber - OrderLine - Product Name
Order-1 null null // (this order has no lines)
Order-2 1 Red widget
I tried this query but is not getting the orders with no lines
var model = (from po in Orders
from line in po.OrderLines
select new
{
OrderNumber = po.Id,
OrderLine = line.LineNumber,
ProductName = line.Product.ProductDescription,
}
)

Here is an article which appears to explain how to achieve exactly what you are trying to do.
public static void OuterJoinSimpleExample()
{
var customers = new List<Customer>() {
new Customer {Key = 1, Name = "Gottshall" },
new Customer {Key = 2, Name = "Valdes" },
new Customer {Key = 3, Name = "Gauwain" },
new Customer {Key = 4, Name = "Deane" },
new Customer {Key = 5, Name = "Zeeman" }
};
var orders = new List<Order>() {
new Order {Key = 1, OrderNumber = "Order 1" },
new Order {Key = 1, OrderNumber = "Order 2" },
new Order {Key = 4, OrderNumber = "Order 3" },
new Order {Key = 4, OrderNumber = "Order 4" },
new Order {Key = 5, OrderNumber = "Order 5" },
};
var q = from c in customers
join o in orders on c.Key equals o.Key into outer
from o in outer.DefaultIfEmpty()
select new {
Name = c.Name,
OrderNumber = ((o == null) ? "(no orders)" : o.OrderNumber)
};
foreach (var i in q) {
Console.WriteLine("Customer: {0} Order Number: {1}",
i.Name.PadRight(11, ' '), i.OrderNumber);
}
Console.ReadLine();
}

This is the working query (built using the example linked above):
var model = (from po in orders
join line in orderLines // note this is another var, it isn't po.Lines
on po.Id equals line.OrderId into g
from line in g.DefaultIfEmpty()
select new
{
OrderNumber = po.Id,
OrderLine = line == null ? 0 : line.LineNumber,
ProductName = line == null ? string.Empty : line.Product.ProductDescription,
}
)

var model =
from po in Orders
from line in po.OrderLines.DefaultIfEmpty()
select new
{
OrderNumber = po.Id,
OrderLine = line != null ?
(int?)line.LineNumber : null,
ProductName = line != null ?
line.Product.ProductDescription : null
} ;

Related

How can I get only records with a unique property using entity framework and linq?

public class Person
{
public string firstName;
public string lastName;
}
I want a list of all Persons with a unique first name.
Persons table
Tom Haverford
Tom Baker
Amy Pond
Amy Santiago
Trish Walker
Chidi Anagonye
The query should return
Trish, Chidi
I've tried using Distinct and a combination of GroupBy and Select, but those return Trish, Chidi, Tom, Amy.
Demo on dotnet fiddle
You can Group by then count number of duplicated items. After that, you can get the item with count value equals to 1 like below.
var arr = new []
{
new Person { firstName = "Tom", lastName = "Haverford" },
new Person { firstName = "Tom", lastName = "Baker"},
new Person { firstName = "Amy", lastName = "Pond" },
new Person { firstName = "Amy", lastName = "Santiago"},
new Person { firstName = "Trish", lastName = "Walker"},
new Person { firstName = "Chidi", lastName ="Anagonye" }
};
var result = arr.GroupBy(p => p.firstName).Select(g => new { Name = g.Key, Count = g.Count()});
foreach(var item in result.Where(p => p.Count == 1))
Console.WriteLine(item.Name);
Output
Trish
Chidi
You can use group by and count functionality together for this :
1. Get a list of all persons from DB :
var personList = (from p in db.Person select p).ToList(); // I assumed here that your db obj name is 'db' and table name is 'Person'
2. Now apply group by query to get the count of first names :
var q = from x in personList
group x by x.firstName into g
let count = g.Count()
select new {Count = count, Name = g.First().firstName };
3. Now you can get your final result like this :
var finalResult = (from p in q where p.Count == 1 select p).ToList();
Happy Coding...

Trying to combine two tables in LINQ

Say I have a
TableA
string Name
string Description
TableB
string Name
string Value
TableA and TableB are joined by Name. (In theory, i.e. not enforced in DB)
I want to create an object:
public MyObject
{
string Name
string Description
List<string> Values
}
I'm tying to understand how to combine these using LINQ.
var tableA = _oda.GetTableA();
var tableB = _oda.GetTableB();
var model = from a in tableA
join b in tableB on a.NAME equals b.NAME
select new MyObject
{
Name= a.Name,
Description = a.Description,
Values = "<Not sure to get list of tableb.Value>"
};
If it's an inner join, you can use GroupBy after your join:
var tableA = new List<TableA> { new TableA { Name = "1", Description = "D1" }, new TableA { Name = "2", Description = "D2"} };
var tableB = new List<TableB> { new TableB { Name = "1", Value = "V1" }, new TableB { Name = "1", Value = "V2"} };
var result = tableA.Join(tableB, a => a.Name, b => b.Name, (a, b) => new { A = a, B = b})
.GroupBy(k => k.A, e => e.B.Value)
.Select(g => new MyObject
{
Name = g.Key.Name,
Description = g.Key.Description,
Values = g.ToList()
});
foreach (var res in result)
{
Console.WriteLine("Name: {0}, Description: {1}, Value: {2}", res.Name, res.Description, string.Join(", ", res.Values));
}
Didn't try the code, but something like this should work.
var result = from a in TableA
join b in TableB on a.Name equal b.Name
group b.Value by a into g
select new MyObject
{
Name = g.Key.Name,
Description = g.Key.Description,
Values = g.ToList()
}

Combining two Lists of an Object into one

I am currently developing an application that requires this senario.
Assuming I have this object
public class ObjectItem
{
public int Id {get;set;}
public int Name {get;set;}
public int Sex {get;set;}
public int Age {get;set;}
public string Complexion {get;set;}
}
If we now have two lists of this object
var studentWithAge = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Age = 2},
new ObjectItem {Id = 2, Name = "Smith", Age = 5},
new ObjectItem {Id = 3, Name = "Juliet", Age = 7},
};
var studentWithSexAndComplexion = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Sex = "Male", Complexion = "fair"},
new ObjectItem {Id = 2, Name = "Smith", Sex = "Male", Complexion = " "},
new ObjectItem {Id = 3, Name = "Juliet", Sex = "Female", Complexion = "Blonde"},
new ObjectItem {Id = 4, Name = "Shittu", Sex = "Male", Complexion = "fair"},
};
I want to merge these two lists into just one. The end result should look like this.
var CompleteStudentData=new List<ObjectItem>
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair",Age=2},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" ", Age=5},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde", Age=7},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair", Age=0},
}
How do i achieve this? Using Union to merge the two list does not produce the desired result. I would appreciate your help.
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) => new ObjectItem
{
Id = sa.Id,
Name = sa.Name,
Age = sa.Age,
Sex = ssc.Sex,
Complexion = ssc.Complexion
}).ToList();
Or, avoiding creation of new objects:
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) =>
{
sa.Sex = ssc.Sex;
sa.Complexion = ssc.Complexion;
return sa;
}).ToList();
And if you want to add students presented only in the second list, than also:
result.AddRange(StudentWithSexAndComplexion.Where(ssc => !StudentWithAge.Any(sa => sa.Id == ssc.Id)));
Since it's possible that your collections will not have a 1-to-1 correspondence, you would have to do a full outer join. See here for how you can compose it that way.
Here's one way you can get similar results.
Collect all the keys (the ids) from both collections, then perform a left join with each of the collections, then combine the results.
var ids = studentWithAge.Select(s => s.Id)
.Union(studentWithSexAndComplexion.Select(s => s.Id));
var query =
from id in ids
from sa in studentWithAge
.Where(sa => sa.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
from ssc in studentWithSexAndComplexion
.Where(ssc => ssc.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
select new ObjectItem
{
Id = id,
Name = sa.Name ?? ssc.Name,
Sex = ssc.Sex,
Age = sa.Age,
Complexion = ssc.Complexion,
};
.Net has a function which is concatenating collections:
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();
var StudentWithAge = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Age=2},
new ObjectItem{Id=2,Name="Smith",Age=5},
new ObjectItem{Id=3,Name="Juliet",Age=7},
};
var StudentWithSexAndComplexion = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair"},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" "},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde"},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair"},
};
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();

How to write sub queries using Linq extension methods with EF 6

I'm new to linq and I have 3 tables with these columns.
Trainee (ID, TraineeName)
Course (ID, CourseName)
TraineeCourseEnrollment (TraineeID, CourseID, EnrolledDate)
I created a stored procedure to get un-enrolled courses using this query.
select *
from Course
where ID not in (select CourseID
from TraineeCourseEnrollment
where TraineeID = #traineeid);
How to write the corresponding linq query to this SQL query using extension methods?
You will have to do two queries, first to retrieve the IDs that you want to exclude and the second to get actual courses:
var excludeIDs = db.TraineeCourseEnrollments.Where(w => w.TraineeID == traineeid).Select(s => s.CourseID);
var courses = db.Courses.Where(w =>!excludeIDs.Contains(w.ID)).ToList();
Something like this:
Extensions methods:
int traineeid = ...;
var query = dc.Courses
.Where(c => ! dc.TraineeCourseEnrollments
.Where(o => o.TrainessID == traineeid)
.Select(o => o.CourseID)
.Contains(c.ID));
LINQ query:
int traineeid = ...;
var query =
from c in dc.Courses
where !(from o in dc.TraineeCourseEnrollments
where o.TraineeID == traineeid
select o.CourseID)
.Contains(c.ID)
select c;
var prospectus = new []
{
new { CourseId = "C1", CourseName = "Database" },
new { CourseId = "C2", CourseName = "HCI" },
new { CourseId = "C3", CourseName = "Op Systems" },
new { CourseId = "C4", CourseName = "Programming" }
};
var enrollment = new []
{
new { TraineeID = "T1", CourseId = "C1", Date = new DateTime(2014, 12, 01) },
new { TraineeID = "T2", CourseId = "C1", Date = new DateTime(2014, 12, 05) },
new { TraineeID = "T1", CourseId = "C3", Date = new DateTime(2014, 12, 01) }
};
var notMatchingQueryStyle = from c in prospectus
where !enrollment.Any(r => c.CourseId == r.CourseId)
select c;
Resharper nags me to "simplify" this using All instead of Any, go figure:
var notMatchingQueryStyle = from c in prospectus
where enrollment.All(r => c.CourseId != r.CourseId)
select c;

LINQ Design time compile error

Can someone please inform me what the correct syntax is for the below query?
I get a design time compile error beginning at the "equals" keyword at the following spot:
&& a.applicationid equals ga.applicationid
with the following error:
"A query body must end with a select clause or group clause"
I understand what the error means, but I can't see what the syntax error is....
public static List<ApplicationConfigurations> GetAppConfigs()
{
try
{
using (wmswebEntities DbContext = new wmswebEntities())
{
IEnumerable<ApplicationConfigurations> myAppConfigs = new IEnumerable<ApplicationConfigurations>();
myAppConfigs = (from a in DbContext.ApplicationConfigurations
join ga in DbContext.groupapplicationconfigurationslk on a.configurationid equals ga.configurationid
&& a.applicationid equals ga.applicationid
join g in DbContext.Groups on g.groupnumber equals ga.groupnumber
where a.ActiveFlag == true
&& ga.ActiveFlag == true
&& g.ActiveFlag == true
select
a.applicationconfigurations,
g.groupnumber).ToList();
return myAppConfigs;
}
}
catch (Exception ex)
{
throw ex;
}
}
The answer to this question has a very good explanation of why you cannot join on two fields in LINQ. It also suggests that you can either use an anonymous type to do the join, or you can simply move one of the conditions into the where clause. He's a quick example I put together in LINQPad to illustrate using a join for one of the conditions and a where for the other, and using join with an anonymous type.
var applicationConfigs = new[] {
new { ApplicationID = 1, ConfigurationID = 1, Name = "Application #1" },
new { ApplicationID = 2, ConfigurationID = 1, Name = "Application #2" },
new { ApplicationID = 3, ConfigurationID = 2, Name = "Application #3" },
new { ApplicationID = 4, ConfigurationID = 2, Name = "Application #4" }
};
var groupApplicationConfigs = new[] {
new { ApplicationID = 1, ConfigurationID = 1, Name = "Group App Config #1" },
new { ApplicationID = 1, ConfigurationID = 1, Name = "Group App Config #2" },
new { ApplicationID = 2, ConfigurationID = 1, Name = "Group App Config #3" },
new { ApplicationID = 3, ConfigurationID = 1, Name = "Group App Config #4" }
};
//JOIN + WHERE
var q = from a in applicationConfigs
join ga in groupApplicationConfigs
on a.ApplicationID equals ga.ApplicationID
where a.ConfigurationID == ga.ConfigurationID
select a;
Console.WriteLine(q);
//ANONYMOUS TYPE
var r = from a in applicationConfigs
join ga in groupApplicationConfigs
on new { a.ApplicationID, a.ConfigurationID } equals
new { ga.ApplicationID, ga.ConfigurationID }
select a;
Console.WriteLine(r);

Categories

Resources