Access one to many relationship data - c#

I am learning to work with LINQ, EF etc. I have 2 auto generated edmx classes course and student. I have written a code to access the course id and course name. With the data I also want to fetch the data of the students names who have enrolled for the courses respectively. Following is my code.
testEntities1 t = new testEntities1();
var u = (from g in t.courses
select new
{
g.C_Id,
g.C_Name,
dd = g.student.Select(r=>r.S_Name)
}).ToList();
Now how to use/get data from the property dd which i have selected which contains all the student names who have enrolled for the course. I am able to put data to variable u. I just don't know how to proceed and get data out of dd. Please help. Following is the line for getting data of the course id and course name. How can I get the data of the student names.
List<course> ui = u.Select(d => new course() { C_Name = d.C_Name, C_Id = d.C_Id }).ToList();

First of all there is no need to fetch the data first into an anonymous type and then add it to your custom type course, you can do that directly as mentioned below. Now, since you have not mentioned the Type of course, I am considering following two possibilites:-
Case 1: If you have student names as public List<string> Students_Enrolled { get; set; } in course i.e. you want to fetch the list of students enrolled then:-
List<course> u = (from g in t.courses
select new course
{
C_ID = g.C_Id,
C_Name = g.C_Name,
Students_Enrolled = g.student.Select(r => r.S_Name).ToList()
}).ToList();
Case 2: If you have student names as public string Students_Enrolled { get; set; } i.e. if you want to fetch comma separated student names then you can do this:-
List<course> u = (from g in t.courses
select new course
{
C_ID = g.C_Id,
C_Name = g.C_Name,
Students_Enrolled = String.Join(",",g.student.Select(r => r.S_Name))
}).ToList();

Related

how to join two tables with comma separated values column in c# linq asp.net mvc

I have two tables Profile and CourseList.
Profile table:
ID | Name | CourseId
-----+--------+----------
1 | Zishan | 1,2
2 | Ellen | 2,3,4
CourseList table:
courseid | coursename
---------+--------------
1 | java
2 | C++
3 | oracle
4 | dot net
Here I am joining the two tables and get results and that result in my View page for particular ID like...
If I call ID = 1 in my view:
Name: Zishan,
course name: java,C++
If if i call ID = 2 in my view:
Name: Ellen,
course name: java,C++,oracle.
So I write this Linq query in my controller:
var account = db.Profile.Where(x => x.PId == pid).FirstOrDefault();
string CourseIDS = string.Join(",",account.CourceId);
var courselist = (from p in db.profile
join co in db.CourceList on p.CourceId equals co.courseId
.ToString() into co1 from co2 in co1.Where(x =>
LanguageIDS.Contains(x.courseId.ToString()))
select new ViewProfileVM
{
courseName = string.Join(",", co2.courseName)
}).FirstOrDefault();
ViewBag.courselist = courselist;
Then I pass ViewBag to view and show results.....
Please help me the linq query is not working and in future i want to add join LanguagesList and CountryList like same as CourceList so please suggest me a simple solution for this issue..
Thanks,
I would second what John M has commented, comma separated values is not a recommended approach in relational Database. But since you have it, you could use following to get your expected result.
var profileList = profiles.SelectMany(x=> x.CourseId.Split(',')
.Select(c=>
new
{
Name= x.Name,
CourseId = int.Parse(c)
}));
var result = profileList.Join(courses,
p=>p.CourseId,
c=>c.Id,
(p,c)=>new {Name = p.Name, Courses= c.CourseName})
.GroupBy(x=>x.Name)
.Select(x=>new
{
Name = x.Key,
Courses = string.Join(",",x.ToList().Select(k=>k.Courses))
});
Where profiles and courses are collection of representiatives classes of your data structures.
public class Profile
{
public int Id{get;set;}
public string Name{get;set;}
public string CourseId{get;set;}
}
public class Coursename
{
public int Id{get;set;}
public string CourseName{get;set;}
}
This would give you expected output as
Name : Zishan
Courses : Java,C++
Name:Ellen
Courses:C++,Oracle,Dot Net
Following is the sample data i used for testing.
var profiles = new Profile[]
{
new Profile{Id = 1, Name = "Zishan", CourseId = "1,2"},
new Profile{Id = 2, Name = "Ellen", CourseId = "2,3,4"}
};
var courses = new Coursename[]
{
new Coursename{Id=1, CourseName = "Java"},
new Coursename{Id=2, CourseName = "C++"},
new Coursename{Id=3, CourseName = "Oracle"},
new Coursename {Id=4, CourseName = "Dot Net"}
};
Hope that helps
It seems that every Profile has zero or more Courses, and every Course belongs to zero or more Profiles: a straightforward many-to-many relationship.
Normally in a relational database, this would be implemented using a junction table: a separate table, with two columns that act as foreign keys: ProfileId and CourseId. In your example:
For Zishan, who has Id 1 we would have [1, 1] and [1, 2], indicating that Zishan attends Courses with Id 1 and 2;
For Ellen with Id = 2, we would have [2, 2] [2, 3] [2,4].
Alas, your database designer chose not to follow this standard database pattern. And you are stuck with the problems.
The best solution depends a bit on how long you will be stuck with this database, how often it will change, how often you will ask for "Profiles with their Courses" and "Courses with their attendants".
Best Solution: add a junction table to the database
The best solution would be a one-time migration of your database: create the junction table and add the [profile.Id, Course.Id] combinations to this list. After that remove the CourseId column.
class ProfileCourse
{
public int ProfileId {get; set;}
public int CourseId {get; set;}
}
var ProfileIdCourseIdCombinations = db.Profiles
.SelectMany(profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),
(profile, splitCourseId) => new ProfileCourse
{
ProfileId = profile.Id,
CourseId = splitCourseId,
});
This has to be done only once. Similar queries in future will be fairly standard. Everyone who is a fluent LINQer, will know how to implement the queries. Disadvantage: the database change.
Sub-optimal solution: mimic the junction table
Quite often people have an intermediate (adapter) layer between the use cases and the actual database. This way you can change the database without having to change the users, or vice versa. The pattern for this adapter layer is quite often called the repository pattern.
The Idea is that you create a Repository class that exposes your tables as IEnumerable sequence of items. The junction table will not be a real table, it will be created whenever it is asked for.
class Repository
{
public Respository(MyDbContext db)
{
this.db = db;
}
private readonly MyDbContext db;
private IReadonlyCollection<ProfileCourse> profilesCourses = null;
public IEnumerable<Profile> Profiles => this.db.Profiles;
public IEnumerable<Course> Courses => this.db.Courses;
public IEnumerable<ProfileCourse> ProfilesCourses
{
get
{
if (this.profilesCourses == null
{
this.profilesCourses = ... // the SelectMany from above
.ToList();
}
return this.profilesCourses;
}
}
}
This way your junction table will be created only once per time you create your Repository. It will only be created when used.
This solution is sub-optimal, because the junction table needs to be recreated every time you'll use it in a new Repository. Furthermore, you can't add Profiles nor Courses using this Repository class. If will be quite some work to create functions to Add / Remove profiles and courses to your Repository. It is possibly easier to recreate the repository.
You'll get your answer by asking the repository instead of your db
var repository = new Repository(db);
var profilesWithTheirCourses = repository.Profiles.GroupJoin(repository.ProfilesCourses,
profile => profile.Id, // from every profile take the Id
profileCourse => profileCourse.ProfileId, // from every profileCourse take the ProfileId,
(profile, profileCourses) => new // take every profile with all its matching
{ // profileCourses, to make a new obhect
// Profile properties; add only those you plan to use:
Id = profile.Id,
Name = profile.Name,
Courses = profileCourses.Join(repository.Courses, // join profileCourse with courses
profileCourse => profileCourse.CourseId, // from profileCourse take CourseId
course => course.Id, // from course take Id
(profileCourse, course) => new // when they match make a new
{ // again: only the properties you actually plan to use
Id = course.Id,
Name = course.Name,
})
.ToList(),
});
Solution for this problem only
If you decide to make a solution for this problem only, you can combine the SelectMany and the Join into one big LINQ statement.
Advantage: quick solution; Disadvantage: difficult to read, to test, and to maintain. Similar problems in future will have a similar sub-optimal solution,
var profilesWithTheirCourses = db.Profiles.SelectMany(
profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),
(profile, splitCourseIds) => new
{
Id = profile.Id,
Name = profile.Name,
Courses = splitCourseIds.Join(db.Courses,
courseId => courseId,
course => course.Id,
(courseId, course) => new
{
Id = course.Id,
Name = course.Name,
})
.ToList(),
});

Query Entity Framework using C# and Navigation Properties

First of all I am new to both C# and EF.
I have created a number of entities with the the Model designer in VS 2015 CE and set the relationships.
I would like to query the entities to return all the customers for a specific Contract (e.g. Contract_ID = 1), along with related properties from the CustomerLocker and ContractCustomer entities (For the CustomerLocker Entity if they are present, or null if they are not). I also have the LockerNumber value from the Contract entity (e.g. 100).
I would be grateful if someone can help with the LINQ query required to select the properties I require. I would prefer to be able to use navigation properties if possible.
So far I am able to select the customers but not able to select properties from the CustomerLocker entity.
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber
}
)
entities shown in VS Model Designer
You could get the HasCard from CustomerLockers by filtering on LockerNumber;
CustomerLockers = cc.Customer.CustomerLockers
The query;
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber,
CustomerLockerHasCard = cc.Customer.CustomerLockers
.Where(x => x.LockerNumber == 1000)
.Select(x => x.HasCard)
}
)
Also, I suggest you to define model classes as known type instead of using anonymous type.
An option would be to get the list of customers instead of just the customer's number :
var myCustomers = (from cc in context.ContractCustomers
where cc.Contract_ID.Equals(contractID)
select new
{
Licencee = cc.IsLicencee,
Added = cc.AddedDate,
Firstname = cc.Customer.FirstName,
Lastname = cc.Customer.LastName,
DOB = cc.Customer.DateOfBirth,
Postcode = cc.Customer.PostCode,
CustomerNumber = cc.CustomerNumber,
listOfCustomers = cc.Customer.ToList() // <-Here, a list
}
)
Then you can use a loop :
foreach(var customer in myCustomers.listOfCustomers)
{
var listOfLockers = customer.CustomerLockers.ToList();
}
But this is more a beginner's way, remember it's always better to take everything you need in a single query, like Stormcloack's answer.
This answer is just to show you how you can dig in the entitys the easy way.

Delete subjects which isn't selected anymore

I'm trying to edit subjects which isn't selected by student anymore. This is how my db looks like:
Using editStudent query I'm selecting items which is edited and using studentSubject I'm retrieving subjects from db.
var editStudent = (from st in _db.Students
where st.Id == student.Id
select new Students
{
Id = student.Id,
Name = student.Name,
Surname = student.Surname,
BirthDate = student.BirthDate,
Gender = student.Gender,
Subject = (from q in student.Subject
select q).ToList()
}).FirstOrDefault();
var studentSubjects = (from q in _db.Subjects
where q.StudentId == student.Id
select q.Name).ToList();
How can I delete subjects which isn't selected anymore? (They will no long be in Subject list from first query)
For example:
in db was 2 subject math and english and user changed these and now he
has only math. first query will return only math in list and second
will return both math and english
You can easily select not used Subjects with
var subjectsNotUsed = _db.Subjects.Where(subject => subject.StudentId == null);
Then (if you are using Entity Framework 6) you can use .RemoveRange() so:
_db.Subjects.RemoveRange(subjectsNotUsed);
_db.SaveChanges();
If you use lower version of EF, you need to delete one by one:
foreach (var subject in notUsedSubjects)
{
_db.Subjects.DeleteObject(subject);
}
_db.SaveChanges();
But I think you should change the database schema. Student shouldnt be related to Subject. You should use table Students just for information about students, and table Subjects just for subjects. Third table (basic M:N relation) you will represent that student has some subject, like that:
DB Diagram
This enables you store each subject and student just once in your database.

Fetch distinct object from a collection using GroupBy on a single column in LINQ to Objects/SQL/Entity

I have a Database table Country with example data as follows:
CountryId CountryName Year
1 UK 2001
2 UK 2003
3 UK 2004
4 USA 2001
5 USA 2005
I have a GetAllCountries() method on my DataAccessLevel:
public static IEnumerable<Country> GetAllCountries()
{
List<Country> countries;
using (var context = new ReportEntities())
{
countries= (from c in context.Countries
select c).ToList();
}
return countries;
}
That should return a list of Country objects which I can then use to bind a DropdDownList to show data. When I bind I use the to pick specific attributes to display from the object. So where I need List so that later I can use it in different data loading methods. For example in LoadCountriesToDdlList():
{
var countries= _transactionService.GetAllCountries();
var distinctcountries = countries.GroupBy(c=> c.CountryName);
_UIDDListCountries.DataSource = distinctcountries ;
}
Expected result for the list:
CountryName
UK
USA
I have tried lot of different ways to edit the query but failed every time. Any ideas?
Tried: GrouppBy, OrderedBy, Distinct(), selecting new object, but no luck. The problem seem that I am trying to return a list of Objects.
If you just want the dropdown to have Distinct country name from the above table you could try the below:
{
var countries= _transactionService.GetAllCountries();
var distinctcountries = countries.GroupBy(c=> c.CountryName);
_UIDDListCountries.DataSource = distinctcountries.Select(g => g.First());
}
The above code first groups all the countries objects based on CountryName and then we assign only the first object of each grouped result to the dropdown datasource.
If you want to customize your text field value you could create an anonymous type and use it. Code below:
{
var countries= _transactionService.GetAllCountries();
var distinctcountries = countries.GroupBy(c=> c.CountryName);
_UIDDListCountries.DataSource = distinctcountries.Select(g => new { CountryID = g.First().CountryID, CountryName = g.First().CountryName ,Text = String.Concat(g.First().CountryName, "--", g.First().Year) }) ;
_UIDDListCountries.DataTextField = "Text";
_UIDDListCountries.DataValueField = "CountryName";
}
NOTE: This works when you are only concerned about showing distinct values of CountryNames in the dropdown and not considering the CountryID and the Year in any case
It sounds like you want this
countries= (from c in context.Countries
select c.CountryName).Distinct()
GroupBy() should give you what you're after:
var grouped = context.Countries.GroupBy(c => c.CountryName);
foreach (var country in grouped)
{
var distinctCountryName = country.Key; //Access field used to group
var firstMatchingCountry = country.First();
var matchingCountriesInAList = country.ToList();
}
You could use the following if only country name matters...as in countryId and year don't.
//Note, you'll probably want to change this function name because it's
//not actually getting all countries anymore
public static IEnumerable<Country> GetAllCountries()
{
using (var context = new ReportEntities())
{
//Note, this LINQ query can also return an IQueryable. This is useful
//if you're querying a database because you'll be doing more logic in SQL
//and transferring less data from your database to memory on your C# machine
IEnumerable<Country> countries =
from c in context.Countries
group c by c.CountryName into countriesGroupedByName
select countriesGroupedByName.First();
return countries;
}
}
Do something like the following if you care about countryId and countryName.
IEnumerable<Country> countries =
from c in context.Countries
group c by c.CountryName into countriesGroupedByName
select countriesGroupedByName.OrderBy(c => c.CountryId).First();

Generating Columns in GridView ( C#, LINQ )

The situation is, that i need to create table in grid view looking like this:
----------| ID---|---Name--|--1/2002--|--2/2002--|--1/2003--|........| 2/2009 |
Cust1--|
Cust2--|
:
:
I have two tables in db - Customers and orders, throught LINQ to SQL DataContext
ID and Name of the customers i´m getting from a simple query
var custInfo = from cust in db.Customers
select new { ID = cust.Id,
FullName = cust.FirstName + " " + cust.LastName }
dataGridOrdersPreview.DataSource = custInfo;
And i need some clue, how to generate that columns in format t/year where t indicates the first or second half of the year, and assign to that generated columns each Customer´s orders in that session of the year ( displaying only costs )
[edit]
As far as now, i´m attempting to something like this:
var orders = from ord in db.Orders
group ord by ord.Id_cust into grouped
let costs = grouped
.Where( s => s.YearSession == session && s.Year == year)
.Select(a => new { Costs = a.Cost ) } )
select new { ID = grouped.Key,
Name = custInfo
.Where( a => a.ID == grouped.Key)
.Select( j => j.Name).Single(),
Cost = ExtensionLibrary.Sum(costs, "\n")
};
( in Cost getting only the summed costs in that year session for each customer )
and then i think about iterating throuhgh the years and sessions and getting
somehow the query results to corresponding columns
while (year <= DateTime.Today.Year)
{
year++;
while (session < 2)
{
session++;
dataGridOrdersPreview.Columns.Add(session +"/"+ year);
col.Add((session +"/"+ year),
orders.Select( a => a.Cost ).ToList() );
/* col is Dictionary<string, List<string> > */
}
session = 0;
}
Here i have generated columns that i want and i have orders in Dictionary where Key is column name and Value are orders in that column, but i need some help binding it to that columns
The way I've seen it done is to create a class that has the properties that you want, for example,
class CustOrders
{
public string CustName {get; set;}
public int Orders2002-1 {get; set;}
public int Orders2002-2 {get; set;}
...
public int Orders2009-1 {get; set;}
}
Then use the System.Windows.Forms.BindingSource, call it say CustOrdsBindingSource and set its DataSource to a list of your new class.
List<CustOrders> myListOfCustOrders = new List<CustOrders>();
/* Code to populate myListOfCustOrders */
CustOrdsBindingSource.DataSource = myListOfCustOrders;
In this case you will have to write the code to convert each result of your query results to an instance of CustOrders and store it in myListOfCustOrders.
Finally, the grid view's data source will also have to be set:
gridView1.DataSource = CustOrdsBindingSource;
The big problem I see with this approach is that you will have to change the CustOrders class every year unless there is some voodoo some can suggest to insert properties into the class at run time.
Either way, I hope this gives you a start.
As long as there will be no updating/adding/deleting of rows, I think I would just generate that grid manually.
Fetch the list of customers and the count of how many sales in what year/session. And then in the form take that list and create the needed columns.

Categories

Resources