Entity Framework and LINQ left join and include on same table - c#

I have the following class structure for my Users and the permissions they're in for the different companies they may be associated to:
public class User
{
public Guid Id { get; set; }
public List<Permission> Permissions { get; set; }
public Company DefaultCompany { get; set; }
}
public class Permission
{
public User User { get; set; }
public Company Company { get; set; }
public int PermissionLevel { get; set; }
}
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
}
This results in three SQL tables. There is a FK between Permission.User_Id > User.Id and Permission.Company_Id > Company.Id. There is no explicit relationship (ie. FK) between User.DefaultCompany and the Company table. This is on purpose due to a legacy choice in our database schema.
I also have a database repository method that grabs a user by it's Id, and includes the full Company record:
public User GetById(Guid Id)
{
return (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
select r)
.SingleOrDefault();
}
This works fine, but it doesn't set the DefaultCompany property. So I tried setting that by changing this method to the following. It's worth pointing out that the Company record that represents the DefaultCompany shares the same ID value as the User.
public User GetById(Guid Id)
{
return (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
join c in this.Context.Companies on r.Id equals c.Id into companies
from company in companies.DefaultIfEmpty()
select new { User = r, Company = company })
.ToList()
.Select(p => { p.User.DefaultCompany = p.Company; return p.User; })
.SingleOrDefault();
}
And this does, in fact, set the DefaultCompany but it has the side effect of not selecting the Permissions list. I can do this all as two separate operations, as in the following code, but I'd rather not hit the database twice if I don't have to.
public User GetById(Guid Id)
{
var u = (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
select r)
.SingleOrDefault();
u.DefaultCompany = (from r in this.Context.Companies where r.Id == u.Id select r).SingleOrDefault();
return u;
}
Is there another way to accomplish this?
Edit: explaining resulting SQL data model and additional example.

There are two possible solutions for this problem.
The cleanest is to use the Fluent API to indicate the model that there is a 1 to 1 relation between User and Company.
Override the OnModelCreating(DbModelBuilder modelBuilder) method of your context.
Inside it, configure the 1 ro 1 relation like this:
modelBuilder.Entity<User>()
.HasOptional(u => u.DefaultCompany)
.WithRequired();
NOTE: with this configuration there is a relationship of 1 user to 0 or 1 default companies (note the Optional in HasOptional). And the default company must have a User on the other side. When a 1 to 1 (or 1 to 0..1) relation is configured, EF will automatically use the PK of the related tables to create the relation between them. You can fine tune the relation using other Fluent API functions
After doing so, you can include the DefaultCompany using Include():
User user = ctx.Users
.Include(u => u.DefaultCompany)
.SingleOrDefault(u => u.Id == userId);
The other, more ugly solution, is to use your second query and include the missing permissions in the projection, to force EF to recover them from the DB.
// ...
select new { User = r, Company = company, Companies = r.Permissions }
// ...

Related

EF Core. Get data from many-many relationship

I'm new to EF Core. I have many-many relationship between 2 tables. In total, I have these 3 tables:
Tenant.
UserTenant.
User.
User has property "email". I want to get all tenants that are related to user by given user email. How to do that?
I would do something like this but I think is bad approach.
var user = await dbContext.Users.FirstAsync(u => u.Email == userEmail);
var userTenants = dbContext.UserTenants.Where(u => u.UserId == user.Id);
etc...
What you could do is to create a Class ex:
public class UserEmailViewModel
{
public int Id { get; set; }
public string Email{ get; set; }
}
and Then
var userEmail = await dbContext.SqlQuery<UserEmailViewModel>("select Id, Email from User U , Tenant T , UserTenant UT where u.id=ut.Userid and t.Id=UT.TenantId ")
Note That you should add Properties to UserEmailViewModel if you want to add column to the query

EF one to many where condition

I have generated below two classes using Entity Framework
public class Persons
{
public string Name { get; set; }
public int personId { get; set; }
//NAVIGATIONL PROP.
public virtual ICollection<streetLivedIn> { get; set; }
}
public class StreetLivedIn
{
public string Address1 { get; set; }
public string AddressType { get; set; }
//NAVIGATIONL PROP.
public virtual PersonId (Foriegn Key with Person)
}
There is a One-to-Many relation between Person and Street Lived In.I am trying to pull a list of persons whose AddressType, in streetlivedin table, is "Home".For this I have the below statement
var Lstpersons = db.persons()
.Include(x => x.streetlivedin)
.Where(x => x.streetlivedin.AddressType == "Home");
The above code throws an error in where clause saying it cannot convert ICollection<streetlivein> to streetlivedin class.
I would like to know how can i achieve this using Include and where.And using Persons context.(I know it can be easily achieved using streetLivedIn context..
like
var Lstpersons = db.streetlivedin()
.Include(x => x.person)
.Where(x => x.streetlivedin.AddressType == "Home");
(No join statements....Please)
You're trying to get find where streetlivedin.AddressType == "Home" but streetlivedin is a collection on the person entity. Instead, do a sub-query on streetlivedin, for example:
var Lstpersons = db.persons()
.Include(x => x.streetlivedin)
.Where(x => x.streetlivedin.Any(y=>y.AddressType == "Home"));
To get all the people with only their home address, you'd want a query something like this.
from person in db.persons()
join address in db.streetlivedin() on person.personId equals address.PersonId
where address.AddressType == "Home"
select new { Person = person, HomeAddress = address }
Of course, since that's an inner join, any person with more than one Home address would show up more than once in the results.
As an aside, the mixed capitalization would drive me nuts if I had to work with that codebase. You don't have to follow the C# coding conventions, but if you're going to pick your own convention at least make it consistent.
int id=1;
var Lstpersons=(from s in db.streetlivedins where s.AddressType == "Home" && s.PersonId ==id select s).ToList();
If you don't want specific user address
var Lstpersons=(from s in db.streetlivedins where s.AddressType == "Home" select s).ToList();
If you want persons
var Lstpersons=(from p in db.persons let st =p.streetlivedins from s in st where s.AddressType == "Home" select p).ToList();

Filter linq/entity query results by related data

I'm using MVC5 EF6 and Identity 2.1.
I have two classes:
public class Incident
{
public int IncidentId {get; set;}
...//Title, Description, etc
public virtual ICollection<FollowedIncident> FollowedIncidents { get; set; }
public virtual ApplicationUser User { get; set; }
}
public class FollowedIncident
{
public int FollowedIncidentId { get; set; }
public string UserId { get; set; }
public int IncidentId { get; set; }
public virtual Incident Incident { get; set; }
public virtual ApplicationUser User { get; set; }
}
So, the users will have the ability to follow an incident. (For starters, I'm not entirely sure if I need the ICollection and public virtual relationship references, but added them just in case for the time being.)
I'm trying to create the query that will show users the results of their followed incidents. In my controller, my query starts like this (I'm using Troy Goode's paging package... i.e. listUnpaged):
IQueryable<Incident> listUnpaged = db.Incidents.OrderByDescending(d => d.IncidentDate);
Then I want to filter by followed incidents. So, I want to show incidents where userId (parameter I pass to it) is equal to UserId in FollowedIncident. I've tried like this (error about conversion to bool from IEnumerable):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.Where(t => t.UserId == userId));
And this (no error, but doesn't filter at all):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.All(t => t.UserId == userId));
To me, it seems it should be as simple as this:
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.UserId == userId));
But, the linq extensions don't seem to like related data child properties? (I apologize for my programming terminology as I haven't quite pieced together all the names for everything yet.)
Anyone know how to accomplish this? It seems I may not even be thinking about it correct? (...since in the past, I've always used related data to supplement or add properties to a result. This will be the first time I want to narrow results by related data.)
Thank you.
Actually you're going about getting the Incidents the wrong way.. since Incident is a navigation property of FollowedIncident you should just use
IQueryable<Incident> listUnpaged = db.FollowedIncidents
.Where(a => a.UserId == userid)
.Select(a => a.Incident)
.OrderByDescending(d => d.IncidentDate);
Another option is to use Any()
IQueryable<Incident> listUnpaged = db.Incidents
.Where(a => a.FollowedIncidents.Any(b => b.UserId == userid)
.OrderByDescending(d => d.IncidentDate);
which would be like saying
Select *
From Incidents
Where Id IN (Select IncidentId
From FollowedIncident
Where UserId = #UserId)

How to query junction table in EF without it being defined (performance issue)

The classic User-UserRole-Role example
The UserProfile class:
public class UserProfile
{
public int Id { get; set; }
public String UserName { get; set; }
public virtual List<Role> Roles { get; set; }
}
The Role class:
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<UserProfile> Users { get; set; }
}
With additional FluentAPI mapping:
modelBuilder.Entity<Role>()
.HasMany(p => p.Users).WithMany(u => u.Roles)
.Map(m =>
m.MapLeftKey("RoleId").MapRightKey("UserId").ToTable("UserInRole"));
Entity Framework Code First generates the database tables UserProfile, Role and UserInRole. UserInRole table contains only RoleId and UserId keys.
Problem/Need: I need a list of users paired with their roles, but only for a specific list of users and roles.
I solved this problem by first getting the users and then get their Roles, but it seems to be much less performant way (as it seems it does many round-trips to the DBMS).
In SQL, I would do this:
select u.UserName, r.Name from UserInRole as ur
left outer join UserProfile as u on ur.UserId = u.Id
left outer join Role as r on ur.RoleId = r.Id
where u.Id in (1, 2, 3) and r.Id in (1, 2, 3, 4, 5)
If possible, how can I do that in EntityFramework? (Note that I do not have the junction table UserInRole mapped in Code First?) None of the LINQ queries I tried generated the two left outer joins from the junction table, and as far as I could see, this would be the fastest way. Correct me if I'm wrong.

EF access navigation properties in model

I have an entity like below
public class Role
{
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, StringLength(30)]
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<RolePermission> Permissions { get; set; }
public bool HasPermission(String code)
{
foreach (var p in this.Permissions)
{
if (p.Permission.Code.Equals(code))
return true;
}
return false;
}
}
and in controller, this code run fine:
for (var p in db.User.Where(u => u.UserId == 1).First().Role.Permissions) { PrintToDebug(); }
but:
User ur = db.User.Where(u => u.UserId == 1).First();
ur.Role.HasPermission("Some_Code_Were_Defined");
then the Permissions list in HasPermission always has a zero length, why and how to solve?
This is occurring because of Entity Framework Lazy Loading. In your first statement, you are specifically requesting the Permissions property, which causes Entity Framework to generate a query that loads that table from the database. In the second query, you are only asking Entity Framework to load the User table from the database, but the HasPermission method you are calling has no way of making another database call to load the Permissions table.
This is a common issue when working with Entity Framework. It can be resolved by using the Include() extension method from Entity Framework to eagerly load the related table in the second query, i.e. User ur = db.User.Where(u => u.UserId == 1).Include(u => u.Role.Permissions).First();

Categories