I have two related tables like below :
Users :
public partial class Users
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Users()
{
}
public int ID { get; set; }
public int UserType_ID { get; set; }
public string Email { get; set; }
public virtual UserTypes UserTypes { get; set; }
}
UserTypes :
public partial class UserTypes
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public UserTypes()
{
this.Users = new HashSet<Users>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Users> Users { get; set; }
}
For access Name of UserType i wrote this linq to entity :
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var user = (from User in entities.Users
//join UserType in entities.UserTypes on User.UserType_ID equals UserType.ID
where User.ID == User_ID_Integer
select User).FirstOrDefault();
if (user != null)
{
UserTypes[0] = user.UserTypes.Name;
}
}
My question is why user.Name does not work for my purpose and what is the benefit of join in linq to entity?
If i remove join as i did in my query i still can see Name field of UserType with user.UserTypes.Name.
You do not need join if you have defined correctly navigation properties. And if you just need Name, do not retrieve full entity.
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var query =
from User in entities.Users
where User.ID == User_ID_Integer
select User.UserTypes.Name;
var name = query.FirstOrDefault();
if (name != null)
{
UserTypes[0] = name;
}
}
If you use navigation property in query, EF automatically generates all needed joins. But if you just select whole entity without defining Include - EF will not load related data. It makes sense, because otherwise you may load almost whole database if there are a lot of relations.
Since you have set up the relations in your entities you don't need to manually write join to load related data:
var user = entities.Users
.Include(u => u.UserTypes)
.Where(u => u.ID == User_ID_Integer)
.FirstOrDefault();
As for your join being useless - EF Core translates the code into actual SQL (which you can check) and since you are not selecting any data from the joined table - it is as useless as it would be in SQL query where you have selected fields only from one table of join result.
Related
I am working on a Blazor Project and using Dapper to Pull Data from a SQL Db.
I am pulling 3 tables at the moment. An entity table, a specialty table and a bridge table that is there to maintain the many to many relationship between entity and specialties.
I am able to pull from SQL fine and I want to combine the data in my data service and inject it as a new object model to the Blazor component.
Here are the models:
Entity
public class EntityModel : IEntityModel
{
public int Id { get; set; }
public int PhysicianId { get; set; }
public int PartnerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Specialty
public class SpecialtyModel : ISpecialtyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
BridgeModel
public class BridgeModel : IBridgeModel
{
public int Id1 { get; set; }
public int Id2 { get; set; }
}
I made the properties in the bridge model generic so I could use it with another bridge table I have for a many to many relationship. The bridge tables are just two columns of IDs that link their respective tables. In this case Entity.Id and Specialty.Id
Here is the model I am combining all the information into:
public class CombinedModel : ICombinedModel
{
public int Id { get; set; }
public int PhysicianId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<ISpecialtyModel> Specialties { get; set; }
}
Here is the inside of my data service where I am stuck trying to combine the data with Linq and Lambda expressions.
public async Task<List<IEntityModel>> ReadEntities()
{
var entities = await _dataAccess.LoadData<EntityModel, dynamic>("dbo.spEntity_Read", new { }, "SqlDb");
return entities.ToList<IEntityModel>();
}
public async Task<List<ISpecialtyModel>> ReadSpecialties()
{
var specialties = await _dataAccess.LoadData<SpecialtyModel, dynamic>("dbo.spSpecialty_Read", new { }, "SqlDb");
return specialties.ToList<ISpecialtyModel>();
}
public async Task<List<IBridgeModel>> ReadEntitySpecialtyBridge()
{
var bridge = await _dataAccess.LoadData<BridgeModel, dynamic>("dbo.spEntitySpecialty_Read", new { }, "SqlDb");
return bridge.ToList<IBridgeModel>();
}
public async Task<List<ICombinedModel>> CombineData()
{
var entities = await ReadEntities();
var specialties = await ReadSpecialties();
var bridge = await ReadEntitySpecialtyBridge();
//var combined = (from e in entities
// join b in bridge on e.Id equals b.Id1
// join s in specialties on b.Id2 equals s.Id
// select new CombinedModel()
// {
// Id = e.Id,
// PhysicianId = e.PhysicianId,
// FirstName = e.FirstName,
// LastName = e.LastName,
// Specialties = new List<ISpecialtyModel>()
// });
var combined = (from e in entities
select new CombinedModel
{
Id = e.Id,
PhysicianId = e.PhysicianId,
FirstName = e.FirstName,
LastName = e.LastName,
Specialties = specialties.Where(s => )
}
);
return combined.ToList<ICombinedModel>();
This is where I am stuck. How can I write this Linq query to combine this data into the new model?
I am able to get data passed into the razor component but I am not combining it correctly and this is where I am stuck.
I hope someone can shed some light on the matter. Thank you for taking the time to look over this, I appreciate it.
With Thanks,
Cesar
If you wanted to process locally (doing on the server should eliminate the need to pull bridge over from the database, and if bridge contains records that aren't relevant to entities could potentially be a lot of unnecessary data traffic) then you just need to filter specialties by the correct bridge records for a given entity:
var combined = (from e in entities
select new CombinedModel {
Id = e.Id,
PhysicianId = e.PhysicianId,
FirstName = e.FirstName,
LastName = e.LastName,
Specialties = specialties.Where(s => bridge.Where(b => b.Id1 == e.Id).Select(b => b.Id2).Contains(s.Id)).ToList()
});
Depending on the size of specialties and entities, it might be worthwhile to pre-process bridge to make access for a given entity more efficient (Where is O(n) so specialties.Where x bridge.Where is O(n*m)):
var bridgeDict = bridge.GroupBy(b => b.Id1).ToDictionary(bg => bg.Key, bg => bg.Select(b => b.Id2).ToHashSet());
var combined = (from e in entities
let eBridge = bridgeDict[e.Id]
select new CombinedModel {
Id = e.Id,
PhysicianId = e.PhysicianId,
FirstName = e.FirstName,
LastName = e.LastName,
Specialties = specialties.Where(s => eBridge.Contains(s.Id)).ToList()
});
I have the following 2 DTOs that are used to return the information in the API in order to filter out fields that I don't want the client to be able to view
public record CommentDto
{
public int? CommentId { get; set; }
public string AuthorUid { get; set; }
public string Text { get; set; }
public int? ParentCommentId { get; set; }
public string CommentPn { get; set; }
public virtual UserDto User { get; set; }
}
public record UserDto
{
public string Uid { get; set; }
public string Username { get; set; }
}
I'm querying the postgres database using the following:
var comments = await dbSet.
Where(c => c.commentPn == sentPn)
.Select(c => new CommentDto
{
CommentId = c.CommentId,
CommentPn = c.CommentPn,
AuthorUid = c.AuthorUid,
Text = c.Text,
ParentCommentId = c.ParentCommentId,
User = new UserDto
{
Username = dbSet.Select(u => u.User.Username).Single(),
Uid = dbSet.Select(u => u.User.Uid).Single()
},
}).ToListAsync();
While this works and the correct data is returned, I notice what I believe are unnecessary call(s) being included in the query.
SELECT d1.comment_id AS "CommentId", d1.comment_pn AS "CommentNiin", d1.author_uid AS "AuthorUid", d1.comment_text AS "Text", d1.parent_comment_id AS "ParentCommentId", (
SELECT u.username
FROM database_item_comments AS d
INNER JOIN users AS u ON d.author_uid = u.uid
LIMIT 1) AS "Username", (
SELECT u0.uid
FROM database_item_comments AS d0
INNER JOIN users AS u0 ON d0.author_uid = u0.uid
LIMIT 1) AS "Uid"
FROM database_item_comments AS d1
I know it's due to the way I'm retrieving the user values being incredibly inefficient, what would be the correct way to complete this query while only making a single call to the users table?
Preferably just querying the comment table directly, and returning the full Comment entity sans DTO, without having to map the variables, while creating the UserDto for the Comment and mapping the values
Your Comment entity should have a reference to a User entity, which would see your query corrected to:
var comments = await dbSet.
Where(c => c.commentPn == sentPn)
.Select(c => new CommentDto
{
CommentId = c.CommentId,
CommentPn = c.CommentPn,
AuthorUid = c.AuthorUid,
Text = c.Text,
ParentCommentId = c.ParentCommentId,
User = new UserDto
{
UId = c.User.UId,
Username = c.User.Username
});
}).ToListAsync();
Your example would potentially fail if you have multiple comments in the DB as to populate the user DTO you were effectively telling it to load all comments (DbSet.Select) to get at the User and expect only 1 result via Single(). Then there is the fact that you are executing that twice, once to select the ID, and then to select the name.
I have 2 models:
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Email { get; set; }
[Required]
[MaxLength(100)]
public string Password { get; set; }
}
and
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public User User { get; set; }
}
I would like to use this query to retrieve all questionnaires of certain user:
List<Questionnaire> questionnaires = this._dbContext.Questionnaires.Where(a => a.User.Id == 1).ToList();
It works, but entity framework produces this sql query:
SELECT `q`.`Id`, `q`.`Title`, `q`.`UserId`
FROM `Questionnaires` AS `q`
LEFT JOIN `Users` AS `u` ON `q`.`UserId` = `u`.`Id`
WHERE `u`.`Id` = 1;
In my opinion, left join is unnecessary. Please is there any workaround to avoid this left join? Thank you in advance.
You will need to expose UserId property on Questionnaire manually:
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
And use it in query instead of a.User.Id:
var questionnaires = this._dbContext.Questionnaires
.Where(a => a.UserId == 1) // use UserId instead of User.Id
.ToList();
For more information:
If you choose not to explicitly include a foreign key property in the dependant end of the relationship, EF Core will create a shadow property using the pattern Id. If you look at the Questionnaire database table, UserId column exists and it has created by EF core as a shadow foreign key.
When you refer User inside where clause _dbContext.Questionnaires.Where(a => a.User.Id == 1), EF Core translate linq query into TSQL left join.
You can also use shadow property do define foreign key:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Questionnaire>()
.Property<int>("UserId");
builder.Entity<Questionnaire>()
.HasOne(e => e.User)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
}
Now left join will be replaced with the inner join:
SELECT [q].[Id], [q].[Title], [q].[UserId]
FROM [Questionnaires] AS [q]
INNER JOIN [Users] AS [c] ON [q].[UserId] = [c].[Id]
WHERE [c].[Id] = 1
To avoid unnecessary join as #Guru Stron said you need to expose UserId property on Questionnaire class.
I have viewModel that extract of multiple model classes. I am binding data and then passing to razor partial view to show data however I am getting error if one of the model object is null. In my business process it is expected however my question is can I use if condition is Linq--Joins i.e. that join result only if data exist in database or is there any better way to do it.
public StudentDetailedProfileViewModel GetStudentDetailedProfileByStudentID(int _studentID)
{
try
{
using (var _uow = new StudentProfile_UnitOfWork())
{
StudentDetailedProfileViewModel StudentProfileObject = new StudentDetailedProfileViewModel();
var _profile = (from _student in _uow.Student_Repository.GetAll()
join _contactDetail in _uow.ContactDetail_Repository.GetAll() on _student.StudentID equals _contactDetail.StudentID
join _addressDetail in _uow.Address_Repository.GetAll() on _student.StudentID equals _addressDetail.StudentID
join _studentCourse in _uow.Course_Repository.GetAll() on _student.StudentID equals _studentCourse.StudentID
join _school in _uow.School_Repository.GetAll() on _studentCourse.SchoolID equals _school.SchoolID
join _campus in _uow.Campus_Repository.GetAll() on _studentCourse.CampusID equals _campus.CampusID
where _student.StudentID == _studentID
select new StudentDetailedProfileViewModel { _studentModel = _student, _contactDetailModel = _contactDetail, _addressModel = _addressDetail , _courseModel = _studentCourse,_schoolModel = _school, _campusModel = _campus}).FirstOrDefault();
_profile._emergencyContactModel = (from _emergencyContact in _uow.EmergencyContact_Repository.GetAll()
where _emergencyContact.StudentID == _studentID
select _emergencyContact).ToList();
return _profile;
}
}//
catch { return null; }
}
......
public class StudentDetailedProfileViewModel
{
public StudentDetailedProfileViewModel() { }
public Student _studentModel { get; set; }
public Course _courseModel { get; set; }
public School _schoolModel { get; set; }
public Campus _campusModel { get; set; }
public ContactDetail _contactDetailModel { get; set; }
public Address _addressModel { get; set; }
public List<EmergencyContact> _emergencyContactModel { get; set; }
}
Instead of JOINing, if your root entity (Student) has navigation properties to the child collections (and the associations are configured in your entity model) you could Include() them. Let LINQ generate the select statement rather than trying to figure it out beforehand.
I am encountered an error that I am not familier with. I tried to google with no success.
I wrote the following query where I am having this error.
The entity or complex type 'MyWebProject.Models.UserDetail' cannot be constructed in a LINQ to Entities query.
The query:
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId
select new UserDetail()
{
FullName = k.FullName,
Email = k.Email,
About = k.About,
Link = k.Link,
UserSchool = new School()
{
SchoolId = k.UserSchool.SchoolId,
SchoolName = k.UserSchool.SchoolName
},
UserCourse = new Course()
{
CourseId=k.UserCourse.CourseId,
CourseName=k.UserCourse.CourseName
},
Country=k.Country
}).FirstOrDefault();
Class:
public class UserDetail
{
public int Id { get; set; }
public int UserId { get; set; }
public string FullName { get; set; }
public string Link { get; set; }
public bool? Verified { get; set; }
public string Email { get; set; }
public string About { get; set; }
public School UserSchool { get; set; }
public Course UserCourse { get; set; }
public string Country { get; set; }
}
public class School
{
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public string Country { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public School School { get; set; }
}
Any idea what went wrong??
It looks like it is due to how you are creating the complex properties School and Course in the middle of the query. It would be better to select the User (remove the select transformation), then use navigation properties to access those objects instead of building them manually. The navigation are meant for this as long as you have the proper relations built with foreign keys.
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId})
.FirstOrDefault();
// access navigation properties which will perform the joins on your behalf
// this also provides for lazy loading which would make it more effecient. (it wont load the school object until you need to access it)
userdata.School
userdata.Course
MSDN article about navigation properties: http://msdn.microsoft.com/en-us/library/vstudio/bb738520(v=vs.100).aspx
This should give you what you want. It will load your objects as part of the query (and not rely on lazy loading).
UsersContext db = new UsersContext();
var userdata = db.UserDetails.Include(x => x.UserSchool)
.Include(x => x.UserCourse)
.Include(x => x.Country)
.Where(x => x.UserId == WebSecurity.CurrentUserId)
.FirstOrDefault();
I think it's because your entity has the same name of the object you're trying to create. Try renaming the object you want to return back. If you want to return the same type as your entity try the eager loading with .Include("relationshipname") feature.
A great answer from #Yakimych is given below.
You cannot (and should not be able to) project onto a mapped entity. You can, however, project onto an annonymous type or onto a DTO:
public class ProductDTO
{
public string Name { get; set; }
// Other field you may need from the Product entity
}
And your method will return a List of DTO's.
public List<ProductDTO> GetProducts(int categoryID)
{
return (from p in db.Products
where p.CategoryID == categoryID
select new ProductDTO { Name = p.Name }).ToList();
}
Mapped entities in EF basically represent database tables. If you project onto a mapped entity, what you basically do is partially load an entity, which is not a valid state. EF won't have any clue how to e.g. handle an update of such an entity in the future (the default behaviour would be probably overwriting the non-loaded fields with nulls or whatever you'll have in your object). This would be a dangerous operation, since you would risk losing some of your data in the DB, therefore it is not allowed to partially load entities (or project onto mapped entities) in EF.
For more details please go to the following link:
The entity cannot be constructed in a LINQ to Entities query