Linq to SQL - Get Related Data - c#

I've got some data that I need to return some of its related data and put it all in a model. I have all the appropriate fields setup in my model, and looks like this:
public class ComputerModel
{
public int MachineId { get; set; }
public int GroupId { get; set; }
public int SoftwareVersionId { get; set; }
public string GroupName { get; set; }
public string SoftwareVersion { get; set; }
public string IPAddress { get; set; }
public string HostName { get; set; }
public string MACAddress { get; set; }
public string Title { get; set; }
public bool IsIGMonitor { get; set; }
public string UpTime { get; set; }
public DateTime DateEntered { get; set; }
public string EnteredBy { get; set; }
public Nullable<DateTime> DateUpdated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<MachineRole> MachineRoles { get; set; }
public ICollection<Role> Roles { get; set; }
}
Here's the linq statement I'm trying to use:
var query = (from m in unitOfWork.Context.Machines
join u in unitOfWork.Context.Users
on m.EnteredBy equals u.UserId into EntByUser
from EnteredByUser in EntByUser.DefaultIfEmpty()
join u2 in unitOfWork.Context.Users
on m.UpdatedBy equals u2.UserId into UpdByUser
from UpdatedByUser in UpdByUser.DefaultIfEmpty()
join g in unitOfWork.Context.Groups
on m.GroupId equals g.GroupId into Grp
from Groups in Grp.DefaultIfEmpty()
join s in unitOfWork.Context.SoftwareVersions
on m.SoftwareVersionId equals s.SoftwareVersionId into SW
from SoftwareVersions in SW.DefaultIfEmpty()
join mr in unitOfWork.Context.MachineRoles
on m.MachineId equals mr.MachineId into MachRoles
from MachineRoles in MachRoles.DefaultIfEmpty()
join r in unitOfWork.Context.Roles
on MachineRoles.RoleId equals r.RoleId into Rolz
from Rolz2 in Rolz.DefaultIfEmpty()
select new ComputerModel()
{
MachineId = m.MachineId,
GroupId = m.GroupId,
SoftwareVersionId = m.SoftwareVersionId,
GroupName = Groups.GroupName,
SoftwareVersion = SoftwareVersions.Version,
IPAddress = m.IPAddress,
HostName = m.HostName,
MACAddress = m.MACAddress,
Title = m.Title,
IsIGMonitor = m.IsIGMonitor,
UpTime = m.UpTime,
DateEntered = m.DateEntered,
DateUpdated = m.DateUpdated,
EnteredBy = EnteredByUser.FirstName + " " + EnteredByUser.LastName,
UpdatedBy = UpdatedByUser.FirstName + " " + UpdatedByUser.LastName,
MachineRoles = m.MachineRoles,
Roles = ?????
}).ToList();
I can get MachineRoles to populate but I cannot get Roles to populate. I've tried Roles = Rolz2 but Rolz returns a single instance of Role, not a collection.
How can I get this query to return Machines and the related data for both MachineRoles and Roles?
I've looked at the following articles but haven't had much luck:
This SO Article
Loading Related Data - MSDN
Using Include with Entity Framework
UPDATE
I notice if I remove my model and use an anonymous type, then I don't get any errors:
select new ()
{
GroupName = Groups.GroupName,
......
}).ToList();
But this doesn't help me in my project because I need to use a Model for the data.

If all the above tables are related via PK-FK relationship you can use linq lambda functions .Include() to include related tables and then use Navigation properties to access data.
If the tables are not related you can use LINQ left joins as shown in http://www.devcurry.com/2011/01/linq-left-join-example-in-c.html.
It looks like you need a mix of inner and left joins. The above example and only achieve inner joins.

Related

Include Properties at All Level

My model is:
public class WhereClause
{
[Key]
public int Id { get; set; }
public int? ColumnId { get; set; }
[StringLength(100)]
public string Logic { get; set; }
[StringLength(250)]
public string Value { get; set; }
public List<WhereClause?> WhereClauses { get; set; }
public short HierarchyCount { get; set; }
}
As you see, model contain self-list of object and i want to include all inner list in any level at one fetch. Based on Entity Framework - Include Multiple Levels of Properties I know that can use Include and ThenInclude but i don't know how many level of inner list in model. currently i store HierarchyCount property at model for this purpose and create string of include like .Include("WhereClauses,WhereClauses.WhereClauses,WhereClauses.WhereClauses.WhereClauses") . but are there any solution better of that?
Update:
WhereClause is part of Report Table and can't load whole WhereClause for every report query:
public class Report:ModelAbstract
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
public Table Table { get; set; }
public List<SelectClause> SelectClauses { get; set; }
[ForeignKey("WhereClause")]
public int? WhereClauseId { get; set; }
public WhereClause WhereClause { get; set; }
}
You can use Recursive CTE for such task. CTE will help to return all children objects then we can filter out root. Under hood, EF Core will correct children collections automatically.
var sql = #"
WITH Clauses AS
(
SELECT
Id
FROM WhereClause
WHERE Id = {0}
UNION ALL
SELECT
w.Id
FROM Clauses c
JOIN WhereClause w ON w.WhereClauseId = c.Id
)
SELECT wc.*
FROM WhereClause wc
JOIN Clauses c ON wc.Id = c.Id";
var whereId = 7;
var whereClause = ctx.WhereClause
.FromSqlRaw(sql, whereId)
.ToList()
.First(w => w.Id == whereId);

Joining multiple tables in LINQ

I am joining several tables to create an object that I will use. From the following LINQ you can see the connections and so far it is good. I am trying to join another table SERVICES to CLIENT.
CLIENT -> SERVICE is one to many relationship , one client can have many services. In Service there is a foreign key User.Id from USERS table where I keep my user emails and ClientID as foreign key as well.
What I am trying to achieve is get this object populated. I need to join and somehow group on client and get the ServiceOwners email from every service a client has in a list of strings.
public class ClientEmailContact
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ClientName { get; set; }
public bool EmailBatchExclusion { get; set; }
public string ClientStatus { get; set; }
public int CsOwnerId { get; set; }
public int ClientId { get; set; }
public List<string> ServiceOwnerEmails { get; set; }
}
var result = (from c in _dataContext.Clients
join ca in _dataContext.Users on c.Id equals ca.ClientId
join a in _dataContext.UserSettings on ca.UserId equals a.Id
select new ClientEmailContact
{
Email = a.Users.Email,
FirstName = a.FirstName,
LastName = a.Surname,
ClientName = c.ClientName,
EmailBatchExclusion = a.Users.EmailBatchExclusion,
ClientStatus = c.ClientStatus,
CsOwnerId = c.ClientServiceOwnerId ?? 0,
//ServiceOwnerEmails = ??? --------- ???
}).AsQueryable();

LINQ JOIN two tables and viewing both columns

I Have two tables
tblPhases
And
tblTruck
I JOINED these two but I can't seem to use both columns on both tables.
Phase.cs
public class Phases
{
public int Id { get; set; }
public int TruckId { get; set; }
public virtual RTrucks Truck { get; set; }
public string Checkin { get; set; }
public string ChassisPrep { get; set; }
public string Floor { get; set; }
public string Body { get; set; }
public string Paint { get; set; }
public string Finished { get; set; }
}
RTruck.cs
public class RTrucks
{
public int Id { get; set; }
public string ChassisManufacturer { get; set; }
public string ChassisModel { get; set; }
}
Service.cs
[OperationContract]
List<Phases> GetPhasesbyTruck(int TruckId);
Service.svc.cs
public List<Phases> GetPhasesbyTruck(int TruckId)
{
TruckDb db = new TruckDb();
List<Phases> r = new List<Phases>();
var join = from p in db.RTrucks
join t in db.Phases
on p.Id
equals t.TruckId
where t.Id == TruckId
select p;
foreach (var item in join)
{
r.Add(new Phases()
{
//this is not the actual coding but just a demonstration of what is
//actually happening
// Phases item like Id = tblTruck item like truckId
});
}
return r;
}
I cannot call the columns from the tblPhases table only from the tbltrucks table, and also how can I use the Phases.cs and RTruck.cs together in my foreach so I can access both class items?
First of all, following code
var join = from p in db.RTrucks
join t in db.Phases
on p.Id equals t.TruckId
select p;
is enough to make a join between two tables. you do not need to add an extra where clause to it. Secondly, you can either select properties from both tables as mentioned by Arianit in his answer or you can just select Phases as in above query and then you can access Truck through navigation properties like
foreach (var p in join){
int truckId = p.Truck.Id;
string chesis = p.Truck.ChassisManufacturer;
string model = p.Truck.ChassisModel;
}
The benefit of second approach is that you don't need a new class to populate properties from both tables. you can access all information through navigation properties.
try this:
var join = from p in db.RTrucks
join t in db.Phases
on p.Id equals t.TruckId
select new {TruckName = p.TruckName, //depends from your table column name
PhasesName = t.PhaseName
};
foreach (var item in join)
{
Console.WriteLine(item.TruckName + "\t" + item.PhaseName);
}
Remove the where from your linq

Entity Framework to json - grouping data

I have a class named Client which looks like this:
public class Client
{
[Key, ForeignKey("BaseAssignments")]
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
And a class named Base looking like this:
public class Base
{
[Key, ForeignKey("BaseAssignments")]
public int BaseId { get; set; }
public string BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
They are to be joined with another class called BaseAssignment:
public class BaseAssignment
{
[Key]
public int BaseAssignmentId { get; set; }
public int BaseId { get; set; }
public int ClientId { get; set; }
public virtual Base Base { get; set; }
public virtual Client Client { get; set; }
}
The idea is that a client can be assigned to many bases, and one base can contain many clients.
Moving forward, I am trying to serialize base entitites in such way that a json representation of a base would have a collection of all it's clients as a subobject. A Web Api method that I'm trying to achieve this is:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = from c1 in db.Clients select new ClientDTO
{
ClientId = c1.ClientId,
CompanyName = c1.CompanyName,
Owner = c1.Owner
}
};
return query;
where a BaseDTO looks like:
public class BaseDTO
{
public String BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public IQueryable<ClientDTO> Clients { get; set; }
}
and ClientDTO looks like:
public class ClientDTO
{
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
}
As of now I'm getting an error stating that ClientDTO is an unexpected type. What can I do to fix this, or maybe the way that I've chosen is completely wrong? Thanks in advance for any insight on this.
EDIT
I've made some changes to the Web Api controller method, so it looks like:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
return query;
This makes the Api produce a JSON, but it still contains a single object for every client, not every base.
You shouldn't have to group or join anything by hand for this, just use a sub-select and have LINQ to the heavy lifting.
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
from c in ba.Client
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
}
Based on StriplingWarrior's suggestion, mixing best of both worlds into:
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
join c in db.Clients on ba.ClientId equals c.ClientId
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
got the JSON I wanted - thanks.

Use multiple conditions in join LINQ. i,e AND

How to use multiple condition in LINQ joins, i.e. in my scenario I need to get all the users from table User where group ID = 4 from table UserInGroup, where UserInGroup is intermediate table between User and Group table as in SQL-T we use join as
select *
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == 4
....
In another approach I am using lambda expression along with LINQ, how I can apply where groupID = 4 in following one??
public IEnumerable<User> GetUsersByGroupID(int _groupID)
{
List<User> _listedUsersByGroupID = new List<User>();
using(var _uow = new UserManagement_UnitOfWork())
{
_listedUsersByGroupID = (from _users in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
select _users).ToList();
return _listedUsersByGroupID;
}
}
User Model
[Table("User")]
public class User
{
public User() { }
[Key]
public int UserID { get; set; }
[StringLength(250)]
[Required]
public string FirstName { get; set; }
[StringLength(250)]
[Required]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[StringLength(250)]
[Required]
public string EmailAddress { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
UserInGroup Model
[Table("UserInGroup")]
public class UserInGroup
{
public UserInGroup() { }
[Key]
public int UserGroupID { get; set; }
[Required]
public int UserID { get; set; }
[Required]
public int GroupID { get; set; }
public User User { get; set; }
public Group Group { get; set; }
}
Group Model
public class Group
{
public Group() { }
[Key]
public int GroupID { get; set; }
[StringLength(250)]
[Required]
public string GroupName { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
You only need to add a condition to filter the users that belong to the group 4. Try this:
_listedUsersByGroupID = (from _user in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
where user.UserInGroup.Any(ug=>ug.groupID==4)
select _user).ToList();
Lambda query would look something like:
ctx.User.Where(user=>
ctx.UserInGroup.Any(userIngroup=>
user.userID == userIngroup.userID && userIngroup.groupID == 4
)
)
That however is just query, if you want to get results add .AsList() or .AsEnumerable() to end.
However you can write silly and inefficient code if you do not fully understand what you are doing. I would reccomend you try this instead:
var publications = ctx.Database.SqlQuery<UserResults>(String.Format(#"
select UserID, FirstName,LastName,Age,EmailAddress,UserInGroup
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == {0}
order by UserID
", Config.Group));
Where Config.Group is 4; UserResults can be User table as well, if you do not want other fields. You need to execute or enumerate over the sql query to use the data and like before you can use .AsList() or .AsEnumerable() for that.
Variable ctx is database context. For example:
using (var ctx = new toxicEntities())
{
}

Categories