Joining multiple tables in LINQ - c#

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();

Related

Why my one-to-zero-one relationship always produce an inner join with entity framework?

My classed are declared as follow:
[Table("Profil")]
public class Profil
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("CodeUtilisateur")]
[MaxLength(25)]
public string UtilisateurId { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
[ForeignKey("UtilisateurId")]
public virtual User User { get; set; }
}
[Table("vwADUser")]
public class User
{
[Key]
public string UserName { get; set; }
[Column("Mail")]
public string Email { get; set; }
}
Here's my linq query.
var query = from profil in icDbContext.Profils
select new
{
profil.Nom,
profil.Prenom,
profil.UtilisateurId,
UserEmail = profil.User != null ? profil.User.Email : ""
};
var result = query.ToList();
The query that is sent to the database produce an inner join with the view [dbo].[vwADUser].
SELECT
1 AS [C1],
[Extent1].[Nom] AS [Nom],
[Extent1].[Prenom] AS [Prenom],
[Extent1].[CodeUtilisateur] AS [CodeUtilisateur],
[Extent2].[Mail] AS [Mail]
FROM [ic].[Profil] AS [Extent1]
INNER JOIN [dbo].[vwADUser] AS [Extent2] ON [Extent1].[CodeUtilisateur] = [Extent2].[UserName]
I have also tried this query with same result.
var query = from profil in icDbContext.Profils
join user in icDbContext.Users on profil.UtilisateurId equals user.UserName into gusers
from guser in gusers.DefaultIfEmpty()
select new
{
profil.Nom,
profil.Prenom,
profil.UtilisateurId,
UserEmail = guser != null ? guser.Email : ""
};
var result = query.ToList();
I tried to manually configure the relationship with a few variants of this command without success.
modelBuilder.Entity<Profil>()
.HasOptional(x => x.User)
.WithOptionalDependent();
Any idea on how to configure my relationship to produce a LEFT JOIN instead of an INNER JOIN?
Try this instead:
[Table("Profil")]
public class Profil
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("CodeUtilisateur")]
[MaxLength(25)]
public string UtilisateurId { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public virtual User User { get; set; }
}
[Table("vwADUser")]
public class User
{
[Key]
[ForeignKey("Profil")]
public string UserName { get; set; }
[Column("Mail")]
public string Email { get; set; }
public virtual Profil Profil {get;set;}
}
This is how you would do it Fluently:
modelBuilder.Entity<Profil>()
.HasOptional(x => x.User) // User is optional for Profil
.WithRequired(z=>z.Profil); // but Profil is required for User

Linq to SQL - Get Related Data

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.

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())
{
}

T-SQL to LINQ Query (Many to Many filter)

I have Enquiries that have a Many to 1 with Clients. Enquiries also have a Many To Many to DisabilityCodes. What I'd like to do is collect all the unique clients that have enquiries that contain a certain disability code. This is my T-SQL:
SELECT DISTINCT ClientId FROM (
SELECT ce.Id as EnquiryId, dc.Code, c.Id as ClientId
FROM ClientEnquiryToDisabilityCodes as etd
INNER JOIN DisabilityCodes as dc
ON etd.ClientEnquiryToDisabilityCode_DisabilityCode = dc.Id
INNER JOIN ClientEnquiries as ce
ON etd.ClientEnquiryToDisabilityCode_ClientEnquiry = ce.Id
INNER JOIN Clients as c
ON ce.ClientEnquiry_Client = c.Id
WHERE dc.Code = 'Ast')
AS data
Could someone even start to show me how to turn this into LINQ? I'm not even sure where to start. Thank you.
It wasn't as difficult as I first thought. I should probably drink MORE coffee before starting to work!
var distinctcients = (from etd in Context.ClientEnquiryToDisabilityCodes
join dc in Context.DisabilityCodes on etd.ClientEnquiryToDisabilityCode_DisabilityCode equals dc.Id
join ce in Context.ClientEnquiries on etd.ClientEnquiryToDisabilityCode_ClientEnquiry equals ce.Id
join c in Context.Clients on ce.ClientEnquiry_Client equals c.Id
where dc.Code == DisabilityCode
select c.Id)
.Distinct().ToList();
Given that you have the class structure attached below, the Example method will achieve what you're attempting
void Example()
{
var clients = new DisabilityCode[1].Where(dc => dc.Code == "Ast")
.SelectMany(dc => dc.Enquiries)
.Select(etd => etd.ClientEnquiry)
.Select(ce => ce.Client.ClientId);
// or
// .Select(ce => ce.Client)
// .Select(c => c.ClientId);
}
public class DisabilityCode
{
public string DisabilityId { get; set; }
public string Code { get; set; }
public virtual ICollection<ClientEnquiryToDisabilityCode> Enquiries { get; set; }
}
public class ClientEnquiry
{
public string ClientEnquiryId { get; set; }
public string ClientId { get; set; }
public virtual Client Client { get; set; }
public virtual ClientEnquiryToDisabilityCode DisabilityCode { get; set; }
}
public class ClientEnquiryToDisabilityCode
{
public string ClientEnquiryId { get; set; }
public string DisabilityCodeId { get; set; }
public virtual ClientEnquiry ClientEnquiry { get; set; }
public virtual DisabilityCode DisabilityCode { get; set; }
}
public class Client
{
public string ClientId { get; set; }
public virtual ICollection<ClientEnquiry> Enquiries { get; set; }
}

Categories