I have a table that has a FK to another table. I'm using a "view model" class that I want to return instead of the actual EF class. I know I can join the 2 tables and filter on the first table and then make a new view model in the select part based on the fields that I want, but can I do this without the join and instead use the linked table (via FK) in the select to create the view model instance?
ie. this works:
return (from a in context.Articles
join t in context.Topics on a.TopicID equals t.Id
where t.Name == topic
select new ArticleViewModel { Title = a.Title, Introduction = a.Introduction, Content = a.Content }).ToList();
Can I somehow create my ArticleViewModel with the Topics.Article link in a short and clean fashion:
return (from t in context.Topics
where t.Name == topic
select /*how can I create ArticleViewModel from t.Articles?*/ t.Articles).ToList();
Not HQL but Linq, I don't think you can do a select many in HQL, so your left with doing the join, or a sub query...
context.Topics.Where(x => x.Name.Equals(topic))
.SelectMany(y => y.Articals)
.Select(z => new ArticalViewModel { Title = z.Title , etc.. });
It seems you have a DbContext
with a DbSet of Articles. Each Article has a Topic.
with a DbSet of Topics. Each Topic has a collection of Articles that belongs to the topic.
And you want all articles that belong to a topic with the value of variable topic.
If you use entity framework and you have a one to many relation like you have in your Topic class, the "many" part has a foreign key to the "one" and the "one" part has a virtual ICollection of the "many" part
An Article belongs to exactly one Topic. An article "has" a Topic.
A topic has a collection of articles. Each item of the collection belongs to the topic.
This is designed as follows:
public class Article
{
public int Id {get; set;} // by convention will become primary key
public int TopicId {get; set;} // by convertion will become foreign key to Topic
public Topic Topic {get; set;} // an article "has" a Topic
public string Title {get; set;}
public string Introduction {get; set;}
// etc.
}
public class Topic
{
public int Id {get; set;} // by convention primary key
public virtual ICollection<Article> Articles {get; set;}
...
// other Topic properties
}
public class MyDbContext : DbContext
{
public DbSet<Topic> Topics {get; set;}
public DbSet<Article> Articles {get; set;}
}
The following Linq statement will get your sequence of ArticleViewModel items
(I put them in small steps so it's clear what happens
using (var context = new MyDbContext(...))
{
IQueryable<Article> articlesOfTopicsWithTopicName = context.Topics
.Where(topc=> topc.Name == myTopicName)
.SelectMany(topic => topic.Articles);
Whenever you have a sequence of type A and each A has a sequence of type B, and you want all B objects, you use SelectMany.
IQueryable<ArticleViewModel> requestedItems =
articlesOfTopicsWithTopicName.Select(article =>
new ArticleViewModel()
{
Title = article.Title,
Introduction = article.Introduction,
...
}
// delayed execution, nothing has been performed yet
return requestedItems.ToList();
// execute and return the list
}
If you use the database profiler, you will see that that selectMany will do a join on the foreign key in articles with the primary key in topic
Related
This is my SQL query:
SELECT TOP 1000
s.id, s.[index], s.staffini,
MIN(accessdate) AS [In],
MAX(accessdate) AS [Out],
sa.mode, sa.daytype
FROM
staffattendances AS sa
INNER JOIN
staffs AS s ON sa.staffid = s.id
GROUP BY
staffid, CAST(accessdate AS DATE), s.staffname,
sa.mode, sa.daytype, s.[index], s.staffini, s.id
How to convert this to a Linq lambda query?
var tmp = Context.staffAttendances
.Include(t => t.staff)
.GroupBy(s => new
{
s.StaffId, s.,
s.AccessDate,
s.staff.StaffName,
s.Mode, s.Daytype,
s.staff.index, s.staff.Id
})
.Select(x => new staffattdto
{
index = x.Key.index,
StaffIni = x.Key.StaffName,
In = x.Max(t => t.AccessDate),
Out = x.Max(t => t.AccessDate),
mode = x.Key.Mode,
daytype = x.Key.Daytype,
})
.OrderByDescending(t => t.In);
I personally prefer using linq query syntax for these kind of queries.
Sample below:
var results = (from o in Organizations
join m in Members on o.OrganizationId equals m.OrganizationId
group o by new { o.OrganizationId, m.MemberId } into g
select new
{
g.Key.OrganizationId,
g.Key.MemberId
})
.Take(1000);
Alas you forgot to mention your classes and the relations between the tables, so I have to make a guess.
So you have Staffs and StaffAttendances. It seems to me that there is a one-to-many relation between Staffs and StaffAttendances: every Staff has zero or more StaffAttendances; every StaffAttendance belongs to exactly one Staff, namely the Staff that foreign key StaffAttendance.StaffId refers to.
You will have classes similar to the following:
class Staff
{
public int Id {get; set;}
public string Name {get; set;}
public string Ini {get; set;}
...
// Every Staff has zero or more StaffAttendances (one-to-many)
public virtual ICollection<StaffAttendance> StaffAttendances {get; set;}
}
class StaffAttendance
{
public int Id {get; set;}
public string Mode {get; set;}
public int DayType {get; set;}
...
// every StaffAttendance belongs to exactly one Staff, using foreign key
public int StaffId {get; set;}
public virtual Staff Staff {get; set;}
}
I'm trying to figure out what your query does. It seems something like: "From every Staf, with its zero or more StaffAttendances give me the Id, the StaffName, (maybe some more Staff properties), and from all its StaffAttendances with the same Mode and DayType give me the minimum and maximum StafAttendance AccessDate"
In entity framework always use Select to query data and select only the properties that you actually plan to use. Only use Include if you plan to change the included data.
The reason for this is that Include will fetch the complete object, inclusive the properties that you won't use. If you have a database with Schools and Students, then every Student will have a foreign key to the School that he attends. Every Student on School [10] will have a foreign key SchoolId with a value [10].
If you use Include to query "School [10] with all its 2000 students" you will be transferring the value 10 more than 2000 times, while you already know this value.
Use the virtual ICollection
I think your query will be something like:
var result = dbContext.Staffs
// only if you don't want all Staffs:
.Where(staff => ...)
.Select(staff => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.StaffAttendances.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// parameter resultSelector, for every [Mode, DayType] combination,
// and all StaffAttendances that have this [Mode, DayType] combination
// make one new:
(modeDayType, staffAttendancesWithThisModeDayType) => new
{
Mode = modeDayType.Mode,
DayType = modeDayType.DayType,
In = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Min(),
Out = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Max(),
},
});
Entity framework knows the relations between your tables, and translates the usage of your virtual ICollection into the proper GroupJoin.
It seems to me that you want some more properties. Since I use entity framework my SQL is a bit rusty, but I guess you'll get the gist.
I think that you don't have a Min / Max for DateTime. If you don't, you'll have to convert them to Ticks and back:
In = new DateTime(staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate.Ticks)
.Min()),
If you are using this more often, consider to create an Extension method for this, they are one-liners, and because you reuse them it might save you a lot of time unit testing them:
public static DateTime MinAccessDate(this IQueryable<StaffAttendance> attendances)
{
return attendances.Select(attendance => attendance.AccessDate).Min();
}
public static DateTime Min(this IQueryable<DateTime> dates)
{
return new DateTime(dates.Select(date => date.Ticks).Min());
}
Usage:
In = staffAttendancesWithThisModeDayType.MinAccessDate(),
Out = staffAttendancesWithThisModeDayType.MaxAccessDate(),
In my opinion, using the virtual ICollection in a one-to-many relationship is the method that is by far the easiest to understand. Unit tests will also be easier, because your input data can come from Dictionaries or Lists instead of from real databases.
Do the GroupJoin yourself
Some people use a version of entity framework that doesn't understand the virtual ICollection, or they prefer to do the GroupJoin to fetch the Staff with their Attendances themselves. The second halve of the query will be similar:
var result = dbContext.Staffs.GroupJoin(dbContext.StaffAttendances,
staff => staff.Id, // from every Staff take the primary key
attendance => attendance.StafId, // from every StaffAttendance take the foreign key
// parameter resultSelector: for every Staff, and its zero or more StaffAttendances
// make one new
(staff, attendancesOfThisStaff) => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.attendancesOfThisStaff.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// etc. see above
});
I am trying to join few tables and return all data from all tables in the list.
Here is the query I tried
List<TblCrrequests> mycr = (from ep in _context.TblCrrequests
join e in _context.TblCrExternalBudget on ep.CrId equals e.CrId
join i in _context.TblCrInternalbudget on ep.CrId equals i.CrId
join t in _context.TblExternalBudgetTypes on e.TypeId equals t.TypeId
where ep.ProjectCode == pcode
select ep)
.OrderByDescending(d => d.RequestedOn)
.Take(8).ToList<TblCrrequests>();
But on executing in this list variable, it only contain data for the main table only. Showing the child table array as empty. How to modify the query to get all those details from the main and child tables as well into the list variable. I am very new to linq..
I tried select ep,e,i,t which didn't work. Also if I don't need all columns from one table, is that possible?
Here is my class defenitions
public partial class TblCrrequests
{
public TblCrrequests()
{
TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
}
public int CrId { get; set; }
public string ProjectCode { get; set; }
public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}
public partial class TblCrExternalBudget
{
public int? CrId { get; set; }
public int? TypeId { get; set; }
public virtual TblCrrequests Cr { get; set; }
public virtual TblExternalBudgetTypes Type { get; set; }
}
public partial class TblExternalBudgetTypes
{
public TblExternalBudgetTypes()
{
TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
}
public int TypeId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}
Use Include() method on a related property you would like to load too. Something like this:
// in this case its assuming that
var books = (from b in db.books.Include(p => p.Authors)
where <your filter criteria>
select b).ToList();
The Include() method instructs it to load book's Authors with it as well.
Alas, you forgot to tell us the classes that you have. You also didn't inform us about the relations between the tables. I'll have to guess from your usage. Next question you ask consider to provide this information.
It seems that you have a table with Requests, and a table InternalBudgets. There seems to be a one-to-many relation between Requests and InternalBudgets: every Request has zero or more InternalBudgets, every InternalBudget belongs to exactly one Request, namely the one that the foreign key refers to.
There seems to be a similar one-to-many between Requests and ExternalBudgets.
Finally there is also a one-to-many between ExternalBudgets and ExternalBudgetTypes. Personally I would expect a many-to-many relation: every ExternalBudgetType is used by zero or more ExternalBudgets. This doesn't change the answer drastically.
If you've followed the entity framework conventions, you'll have classes similar to the following:
class Request
{
public int Id {get; set;}
...
// Every Request has zero or more InternalBudgets (one-to-many)
public virtual ICollection<InternalBudget> InternalBudgets {get; set;}
// Every Request has zero or more ExternalBudgets (one-to-many)
public virtual ICollection<ExternalBudged> ExternalBudgets {get; set;}
}
The Internal and External budgets:
class InternalBudget
{
public int Id {get; set;}
...
// Every InternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
}
class ExternalBudget
{
public int Id {get; set;}
...
// Every ExternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
// Every ExternalBudget has zero or more ExternalBudgetTypess (one-to-many)
public virtual ICollection<ExternalBudgetType> ExternalBudgetTypes {get; set;}
}
Finally the ExternalBudgetTypes:
class ExternalBudgetType
{
public int Id {get; set;}
...
// Every ExternalBudgetType belongs to exactly one ExternalBudget, using foreign key
public int ExternalBudgetId{get; set;}
public virtual ExternalBudget ExternalBudget {get; set;}
}
Because you stuck to the conventions, this is all that entity framework needs to detect your tables, the relations between the tables and the primary and foreign keys
In entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
The foreign key is a column in the table. Hence it is non-virtual. The object that the foreign key refers to is not part of the table, hence if is virtual.
The "Many" side in xxx-to-many relations should be implemented with ICollection<...>, not with IList<...>. Ilist provide functionality that is undefined in databases:
Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];
Can you say which internalBudget has list index [3]?
Better not provide functionality that is not defined. ICollection<...> give you the possibility to Add / Delete / Count, all functionalities that have a defined meaning in a database.
One final tip: in your identifiers use plural nouns to refer to sequences; use singular nouns to refer to elements in these sequences. That makes LINQ statements easier to understand.
Back to your question
Requirement Given a projectCode, give me the requests that have this projectCode, with several properties of their Internal and External Budgets
Easy method: use the virtual ICollections
var projectCode = ...
var result = dbContext.Requests
// Keep only the Requests that have projectCode
.Where (request => request.ProjectCode == projectCode)
// order the remaining Requests by descending RequestedOn date
.OrderByDescending(request => request.RequestedOn)
// Select the properties that you want:
.Select(request => new
{
// Select only the Request properties that you plan to use
Id = request.Id,
Name = request.Name,
...
// Internal budgets of this Request
InternalBudgets = request.InternalBudgets.Select(budget => new
{
// Again, only the properties that you plan to use
Id = budget.Id,
...
// not Needed, you know the value
// RequestId = budget.RequestId,
})
.ToList(),
// External budgets of this Request
ExternalBudgets = request.ExternalBudgets.Select(budget => new
{
...
ExternalBudgetTypes = budget.ExternalBudgetTypes
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
In words: from all Requests, keep only those Request that have a value of property ProjectCode equal to projectCode. Order the remaining Requests by descending value of property RequestedOn. Finally Select several properties of the Request, its internal budgets and its external budgets.
Entity framework knows the relations between the tables, and knows how to translate the use of your virtual properties in the correct (Group-)join.
Note: the result differs slightly from your solution. Your solution would give:
Request InternalBudget (for simplicity: leave out Externalbudget / type
01 10
02 12
04 11
01 14
01 15
02 13
My Solution gives:
Request 01 with its InternalBudgets {10, 14, 15}
Request 02 with its InternalBudgets {12, 13}
Request 05 without any InternalBudgets
Request 04 with its InternalBudget {11}
IMHO this is much more intuitive and more efficient: the properties of each Request items are transferred only once.
Note that you will also get the Requests that have no Internal/External budgets or types.
If you don't want them, use a Where(... => ...Count != 0) to filter them out.
If you don't want "Items with their Sub-items" (which is in fact a GroupJoin), but a standard join, use SelectMany to flatten the result
Do the Join yourself
Some people don't like to use the virtual ICollection<...>. They prefer to do the (Group-)Joins themselves. In method syntax Joins with more than two tables look horrible, luckily you don't have to do a Join.
var result = dbContext.Requests
.Where (request => request.ProjectCode == projectCode)
.OrderByDescending(request => request.RequestedOn)
.Select(request => new
{
// Request properties:
Id = request.Id,
Name = request.Name,
...
// Internal budgets:
InternalBudgets = dbContext.InternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
})
.ToList(),
// External budgets:
ExternalBudgets = dbContext.ExternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
BudgetTypes = dbContext.BudgetTypes
.Where(budgetType => budgetType.ExternalBudgetId == budget.Id)
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
I'm not sure if you can convince your project leader that this method is better readable, more reusable, easier to test and change than the other one with the virtual ICollections.
I am trying to implement Insight.Database in a project, and have run into a brick wall trying to utilize the automatic interface implementation AND mapping object properties to odd column names in the database.
I have the following structure...
class Employee
{
string EmployeeCode {get; set;}
string Name {get; set;}
}
class Vacation
{
int VacationId {get; set;}
DateTime VacationDate {get; set;}
string EmployeeCode {get; set;}
string ApprovedByEmployeeCode {get; set;}
Employee Employee {get; set;}
Employee ApprovedByEmployee {get; set;}
}
My database looks like....
Table: Employees (EmployeeCode, [Name])
Table: Vacations (VacationId, VacationDate, EmployeeCode, ApprovedByEmployeeCode)
View: VacationsView (so I don't have to keep writing [and changing]
the same SELECT over and over)
SELECT v.VacationId, v.VacationDate, v.EmployeeCode, v.ApprovedByEmployeeCode, e1.EmployeeCode AS CreatedByEmployeeCode, e1.[Name] AS CreatedByName, e2.EmployeeCode AS ApprovingEmployeeCode, e2.[Name] AS ApprovingName
FROM Vacations v
INNER JOIN Employees e1 ON v.EmployeeCode = e1.EmployeeCode
INNER JOIN Employees e2 ON v.ApprovedByEmployeeCode = e2.EmployeeCode
Stored Procedure: GetAllVacations
SELECT * FROM VacationsView
Finally, with Insight.Database, I am trying to have an interface that will autopopulate my objects and tell it how to use the different column names from my stored procedure for the "employee" properties.
public interface IMyRepository
{
IList<Vacation> GetAllVacations();
}
....
var repo = conn.As<IMyRepository>();
return repo.GetAllVacations();
This works (as in doesn't error) and all the properties of my vacation are correctly mapped, but my two "employee" properties are null (as expected because the column names don't line up to the property names of an employee object). What I can't figure out is how to tell Insight "Use CreatedBy.." fields to build the "Employee" property and "Approving..." fields to build the "ApprovedByEmployee" property.
I have been able to accomplish it using OneToOne with a callback and columnOverride and then use a standard Query(). I.E..
var vacationStructure =
new OneToOne<Vacation, Employee, Employee>(
callback: (vacation, createdBy, approvedBy) =>
{
vacation.Employee = createdBy;
vacation.ApprovedByEmployee = approvedBy;
}, columnOverride: new ColumnOverride[]
{
new ColumnOverride<EmployeeModel>("CreatedByEmployeeCode", "EmployeeCode"),
new ColumnOverride<EmployeeModel>("CreatedByName", "Name"),
new ColumnOverride<EmployeeModel>("ApprovingEmployeeCode", "EmployeeCode"),
new ColumnOverride<EmployeeModel>("ApprovingName", "Name")
});
....
var results = await conn.QueryAsync("GetAllVacations", new {employeeCode}, Query.Returns(_vacationStructure));
However, I'm really trying to utilize the auto interface capabilities of Insight.
Is what I'm trying to do possible?
Assembling repeated child objects isn't something that's currently done automatically, and the interface implementation doesn't give you the right hooks to override the behavior.
Some options:
A. Change the shape of your result set to return the employees as a list with properties.
B. If the classes aren't sealed, derive from Employee so Insight can differentiate between the classes:
public class AssigningEmployee : Employee {
public string AssigningName { get { return Name; } set { Name = Value; } }
...
}
These solutions are all meh. The whole point of Insight.Database is to just work without a lot of extra work. So...
I opened a github issue to track this:
https://github.com/jonwagner/Insight.Database/issues/384
I have 2 tables with relation
Customer
Id, Nbr, Name
Assignments
Id, CustomerId, Location, AssigmentTime
There is a relation on Customer.Id = Assigments.CustomerId
Every Customer can have lot of assignments, but I am only interested in the last Assignment according to DateTime field AssigmentTime
In SQL it should be a query like:
Select Top 1 From Customer c
Inner Join Assigments a On c.Id = a.CustomerId
Where c.Nbr = 1234
Order By AssigmentTime Desc
I have a problem to construct proper Lambda query.
This code works, but it’s not very effective:
var customerNbr = 1234:
var cst = context.Customers.FirstOrDefault(x => x.Nbr == customerNbr);
if (cst != null && cst. Assigments.Count > 1)
{
cst. Assigments = new List<Assigments>
{
cst.Assigments.OrderByDescending(x => x.AssigmentTime).FirstOrDefault()
};
}
How can I get Customer with just 1 top Assigments in Customer.Assigments List property?
For example:
var lastAssignment = customers.Where(x => x.Nbr == customerNbr)
.SelectMany(x => x.Assignments)
.OrderByDescending(x => x.AssignTime)
.FirstOrDefault();
If you have set-up your entity framework according to the proper coding conventions you'll have designed the one-to-many relation as follows:
class Customer
{
public int Id {get; set;} // primary key
// a Customer has zero or more Assignments
public virtual ICollection<Assignment> Assignments {get; set;}
public int Nbr {get; set;}
... // other properties
}
class Assignment
{
public int Id {get; set;} // primary key
// every Assignment belongs to one Customer via foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
public DateTime AssignmentTime {get; set;}
... // other properties
}
public MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Assignment> Assignments {get; set;}
}
If you've set-up the one-to-many like this, then this is all entity framework needs to know that you designed a one-to-many relationship. If you didn't want to follow the naming conventions, you probably have used fluent API or attributes to configure the one-to-many.
Get the Customer with Nbr = 1234 with his last (newest) Assignment:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new
{
// select the customer properties you will use, for instance
CustomerId = customer.Id,
CustomerName = customer.Name,
// you only want the newest assignment:
NewestAssignment = customer.Assignments
.OrderByDescending(assignment => assignment.AssignmentTime)
.Select(assignment => new
{ // take only the Assignment properties you will use:
Location = assignment.Location,
AssignmentTime = assignment.AssignmentTime,
}
.FirstOrDefault(),
});
}
}
If you are certain there is at utmost one customer with Nbr = 1234, you can end with SingleOrDefault; otherwise your result will be the sequence of Customers with this Nbr.
Each customer will only have the customer properties you will use, and the properties of the newest Assignment you will use. Efficient!
Thank you for your suggestion Harald. I was on to same thig, but I found anonymous object to be a bit bloated. In my case I use EF.Reverse.POCO Generator, so every object is strictly mapped to DB. Customer and Assignments are in reality something else – tables with lot of columns. I can’t have anonymous object as a return from this function.
I could still do something like this:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new Customer
{
// select the customer properties you will use, for instance
Id = customer.Id,
Nbr = customer.Nbr,
Name = customer.Name,
//…and lot of other property mapping
// you only want the newest assignment:
Assignments = new Collection<Assignments>
{
customer.Assignments.OrderByDescending(assignment => assignment.AssignmentTime)
.FirstOrDefault()
}
});
}
}
The anonymous Customer generations will result in lot of property mapping. That’s the minor issue.
Even if I skip Assignments property, this solution with typed object in Select generates an exception inside the result:
Message = "The entity or complex type 'MyNamespace.Customer' cannot be constructed in a LINQ to Entities query."
If I use anonymous object the same code works fine, but as I wrote above – I need typed objects as return.
I want to do Inheritance for similar tables.
For example suppose we have two tables: Teachers and Students both of them can be derived from a base class Human.
Is it possible to write a common task and avoid repeat code using EntityFramework? a function like this which works for DbSet<Student> and DbSet<Teacher>:
void IncreaseAge(DbSet<Human> humans, int id)
{
//
}
and more important, a generic add function to db, pseudo code:
void AddHuman({...}, string name, int age)
{
// add a human to related table
}
any help would be greatly appreciated.
Use Extension with a Generic parameter:
void IncreaseAge<T>(DbSet<T> entities, int id) where T: Human
{
var current = entities.Find(id);
current.Age++;
// SaveChanges() in the context
}
If your Student is inheriting Age property from Human class. This code should work perfectly.
Edited
Also, you can apply the same technique for the add
void Insert<T>(DbSet<T> entities, string name, int age) where T: new(), Human
{
entities.Add(new T{ Name = name, Age = age });
// SaveChanges() in the context
}
Hope this help!
A DbSet represents all the entities. You'd normally not pass it to a function. Instead consider a method like this in your DbContext:
public void IncreaseAge(IQueryable<Human> humans)
{
foreach( var h in humans)
{
h.Age++;
}
SaveChanges();
}
Then you can pass in an query that specifies a set of teachers or students you'd like to operate on. EG:
db.IncreaseAge(db.Teachers.Where(t => t.Age == 47));
I guess you have read about the various strategies when using inheritance in Entity Framework. The nice thing about entity framework is that it hides the used inheritance strategy.
As the three inheritance strategies are described in the link, there is no need to describe the fluent API needed for this. I'll only write the classes you'll end up with and how to do queries on Students, Teachers and the common base class Persons.
Furthermore I'll describe some considerations needed to properly select the correct inheritance strategy.
So you have Student and Teachers, both derived from Person.
abstract class Person
{
public string Name {get; set}
public Gender Gender {get; set;}
public DateTime Birthday {get; set;}
}
public class Teacher : Person
{
public int Id {get; set;}
...
}
public class Student : Person
{
public int Id {get; set;}
...
}
And the DbContext:
public class MyDbContext : DbContext
{
public DbSet<Teacher> Teachers {get; set;}
public DbSet<Student> Students {get; set;}
}
Now whenever you have an IQueryable of Teachers or Students you can also use all properties of Person, without doing something special.
var maleTeachers = myDbContext.Teachers
.Where(teacher => teacher.Gender == Gender.Male);
var youngStudents = myDbcontext.Students
.Where(student => student.Birthday > new Datetime(2000, 1, 1);
If you have to query all Persons, you'll have to Concatenate the Students and the Teachers. In baby steps:
IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
.Select(person => ...)
... etc
Of course this can be done in one statement.
Decide which inheritance strategy to use
When deciding on the inheritance strategy, keep in mind what kind of queries you'll do most:
Query Teachers who ... or Query Students who ...
Query Persons that ...
If you do the first most often, then consider Table per concrete class (TPC). You'll have two tables, one for students and one for teachers. The Person properties are in the same table. So table Students will have Person columns; table Teachers will also have Person columns.
The advantage is that if you ask for "Students who ...", only one table is involved. a join won't be necessary.
The disadvantage is that if you ask for "Persons that ...", the Teachers and Students tables need to be concatenated.
If you will be querying Persons more often, consider creating Table per Type (TPT). The result will be three tables with foreign keys: Teachers, Persons, Students. When asking for Persons, only one table is involved. However, when asking for Teachers we always need to join two tables.
Even if you opt for the best inheritance strategy because those are the kind of queries you perform most often you might sometimes have to do the other type of queries.
TPC: Table per concrete class
You choose this if you mostly ask for Teachers that ... or Students who ... You'll end up with two tables: one for Students, and one for Teachers. There will be no Persons table.
If you have to do an occasional Person query you'll have to concatenate the two sequences. This is in baby steps:
IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
.Select(person => ...)
... etc
Of course this can be done in one statement.
If you have to do this more often consider adding a prperty to your DbContext class that does this for you:
class MyDbcontext : Dbcontext
{
public DbSet<Teacher> Teachers {get; set;}
public DbSet<Student> Students {get; set;}
public IQueryable<Person> Persons
{
get
{
return this.Teachers.Cast<Person>()
.Concat(this.Students.Cast<Person>());
}
}
}
Usage will be:
using (var myDbContext = new MyDbContext(...))
{
IQueryable<Person> females = myDbcontext.Persons
.Where(person => person.Gender == Gender.Female);
}
If you don't want to pollute your Dbcontext, consider creating an extension function that does the same. See extension functions demystified
static class MyDbContextExtensions
{
IQueryable<Person> Persons(this MyDbContext dbContext)
{
return dbContext.Teachers.Cast<Person>()
.Concat(dbContext.Students.Cast<Person>());
}
}
TPT: table per type
You'll end up with three tables, Students, Teachers, Persons. Students and Teachers will have a foreign key to the Persons they are.
You can query Persons directly using the DbSet. On the other hand, if you want only Teachers who..., you'll just access the DbSet. As soon as you use one of the inherited Person properties, Entity framework will automatically do the join for you. You won't see this in the code. So even though you don't do a join, internally two tables are involved, which might be less efficient.
So be careful which inheritance strategy you choose.