Say the EF6 code first is used. Say we have a simple two table relationship.
Where a patient has one doctor associated.
class Patient
{
[Key]
public int id { get; set; }
public string Name { get; set; }
[ForeignKey("Doctor")]
public int DoctorId { get; set; }
public virtual MedicalPersonel Doctor { get; set; }
}
class MedicalPersonel
{
[Key]
public int id { get; set; }
public string Name { get; set; }
}
There is a function Include(string path) to load the Doctor, when I am loading the patient. But what would be the opposite of include if I don't want to load the Doctor if the doctor contains some big fields like images?
Thanks!
That's the fun part about lazy loading, you won't load the Doctor unless you access the property. To be entirely sure you don't 'accidentally' lazy load anything you can
Remove the virtual keyword all together from the properties you never want lazily loaded.
When you create your context, disable lazy loading for that instance of the DbContext:
myContext.Configuration.LazyLoadingEnabled = false;
I think option number 2 would be preferred as that leaves you the choice when to enable/disable lazy loading.
As docs you can use following code in your DbContext.OnModelCreating:
modelBuilder.Entity<Patient>()
.Ignore(p => p.Doctor);
Remove the virtual Key word which will Lazy load the entity. Then when you fetch the Patient entity from the database it will not load the related Doctor entity unless you 'Include' it in the query. https://msdn.microsoft.com/en-gb/data/jj574232.aspx
Related
In my .net 6.0 project I use the Entity Framework 6 to get data from my database.
My model contains a foreign key, which is used to resolve the data of the id.
I use the following call to resolve the foreign key:
// ...
_context.Tools.Include(t => t.User).ToListAsync();
// ...
My Tool Model looks like this:
[Table("MY_TOOLS")]
public class Tool
{
[Key]
[Column("ID")]
public int Id { get; set; }
[Column("UPDATED_BY")]
public int? UpdatedBy { get; set; }
[ForeignKey("UpdatedBy")]
public User? User { get; set; }
}
My User class looks like this:
[Table("MY_USERS")]
public class User
{
[Key]
[Column("ID")]
public int Id { get; set; }
[Column("EMAIL")]
public string? Email { get; set; }
}
When I leave the include like described above, the user is resolved correctly.
Is there a way to remove the user property from the loaded data, when I don't explicitly tell the Model to resolve the foreign key?
It shouldnt resolve it by default as it uses lazy loading. You would have to query it specifically for the user object to get it e.g. _context.My_Tools.include(uvar = uvar.User).FirstOrDefault();
So you just make a method called getToolEager() and one called getTool()
it would be a "waste" of a call to query for the user object only to throw it away in case you might not need it.
You have 2 options:
Lazy Loading Proxy
Use Lazy Loading as described here:
https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy
After just load data as _context.Tools.ToListAsync(); and users will load when you try access them.
Manually load related data
Modify Tool to explicitly store User FK:
[Table("MY_TOOLS")]
public class Tool
{
[Key]
[Column("ID")]
public int Id { get; set; }
[Column("UPDATED_BY")]
public int? UpdatedBy { get; set; }
[ForeignKey("UpdatedBy")]
public User? User { get; set; }
public int? UpdatedBy{ get; set; }
}
So when you load data as _context.Tools.ToListAsync(); fieldUser will be null but UpdatedBy will have User Id(if FK is not null in DB), so you can manually load them manually like tool.User = await _context.Users.FirstOrDefaultAsync(t => t.Id == tool.UpdatedBy);
I am using the code-First approach of MVC facing the below issue.
I have 1 The Admin model which has 2 properties that represent a separate class as shown below respectively.
public class Admin
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public List<IdProof> IdProofDocs { get; set; }
public Subscription subscription { get; set; }
}
public class IdProof
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string File { get; set; }
}
public class Subscription
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public double TotalAmount { get; set; }
public double PaidAmount { get; set; }
}
When I try to save it, it successfully saves values in 3 tables including the last 2 (i.e. IdProof, Subscription) of the Admin Model. see below code
[HttpPost]
public ActionResult AddHotel(Admin admin)//saving correctly in 3 table i.e. admin, subscription,IdProof
{
dbContext.Admins.Add(admin);
dbContext.SaveChanges();
return RedirectToAction("someActionMethod");
}
till now it's good, but from here
when I try to get records by using the below code
public ActionResult AllAdmins()
{
List<Admin> ListAdmin= dbContext.Admins.ToList();//here its fetching records from admin table only ;(
return View(ListAdmin);
}
It gives me only Admin table data, not the other 2 tables*** i.e IdProof & Subscription. I was wondering that EF automatically saves data in the other 2 tables by its model, so at the time of fetching why it's not giving me data from the other 2 tables.
I am using the Code-First approach in mvc5, what is needed to change in my code. I am new in mvc.
Thanks in advance
You need to include other tables if you want EF to load it for you.
List<Admin> ListAdmin= dbContext.Admins
.Include(x => x.IdProofDocs).Include(x => x.Subscription).ToList()
EF is having concept of Lazy Loading and Eager Loading.
Lazy Loading
In Lazy loading EF not load the related entities until ask for it.
Link: https://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx
Eager Loading
In Eager Loading, Related Entity mentioned at the Query time using Include method.
Link : https://www.entityframeworktutorial.net/eager-loading-in-entity-framework.aspx
Depend on requirement, you can choose option.
I'm having issues querying for a specific property using linq in EF.
To outline, Users have associated roles. Each Role has Associated groups. I'm just trying to get the MAMUserGroup property of all the groups associate with the user. I can easily get the associate Roles with .Include(), but am having trouble going the one extra level down to the associated MAMUserGroups.
User model:
public class User
{
[Display(Name = "SSO")]
[Required]
[StringLength(9, ErrorMessage = "SSO must be 9 numbers", MinimumLength = 9)]
public virtual string ID { get; set; }
[Display(Name = "First Name")]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[Required]
public string LastName { get; set; }
[Display(Name = "MAM Roles")]
public ICollection<MAMRoleModel> MAMRoles { get; set; }
}
MAMRole Model:
public class MAMRoleModel
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<MAMUserGroupModels> MAMUserGroups { get; set; }
}
MAM Group Model:
public class MAMUserGroupModels
{
public int ID { get; set; }
public string MAMUserGroup { get; set; }
}
I've tried
foreach(var bar in user.MAMRoles)
{
foreach(var foo in bar.MAMUserGroups)
{
//do something
}
}
But got null reference error. I've also just tried from haim770
var test = db.Users.Include(x => x.MAMRoles.Select(y=> y.MAMUserGroups));
but MAMUserGroup is a count of 0, so it's not seeing the reference.
I think the problem arises because you didn't declare your ICollection in the one-to-many relations in entity-framework as virtual. Declaring the ICollection virtual would solve it
To prevent future problems, consider to make your classes more entity-framework compliant. This diminishes the use of all kinds of attributes. Proper use of plurals and singulars improves readability of the queries which helps those who have to change the code in future.
The primary key 'ID' should not be virtual
In your one-to-many use the virtual ICollection on the one-side, and add add a reference to the one and the foreign key on your many-side
Consider using standard naming conventions. This helps entity-framework to define a model without you having to help it using all kinds of attributs.
Only deviate from the standard naming conventions if you really have to. In that case add Attributes fro primary key, foreign key, one-to-many relation etc, or consider using fluent API.
.
public class User
{
// Standard naming convention: automatic primary key
public string ID { get; set; }
// a user has many MAMRoles.
// standard naming convention: automatic one-to-many with proper foreign key
// declare the collection virtual!
public virtual ICollection<MAMRole> MAMRoles { get; set; }
}
public class MAMRole
{
// standard naming cause automatic primary key
public int ID { get; set; }
// a MAMRole belongs to one user (will automatically create foreign key)
// standard naming cause proper one-to-many with correct foreign key
public string UserId {get; set;}
public virtual User User {get; set;}
// A MamRole has many MAMUserGroupModels
// same one-to-many:
// again: don't forget to declare the collection virtual
public virtual ICollection<MAMUserGroupModel> MamUserGroupModels{ get; set; }
}
public class MAMUserGroupModel
{
// automatic primary key
public int ID {get; set;}
// a MAMUserGroupModel belongs to one MAMUser
// automatic foreign key
public int MAMUserId {get; set;}
public virtual MAMUser MAMUser {get; set;}
}
By the way. Entity framework knows it needs to get the value of a property as soon as you use it in your IQueryable. Only if you want to select property values and get them to local memory you'll need to use Include. This makes the query more efficient as only the used values are selected.
So if you only want to do something with the MamUserGroup inside your MamUserGroupModel, don't include anything:
In baby steps:
IQueryable<NamRole> mamRolesOfAllUsers = myDbContext.Users
.SelectMany(user => user.MamRoles);
IQueryable<MamRoleModel> mamUserGroupModelsOfAllUsers = mamRolesOfAllUsers
.SelectMany(mamRole => mamRole.MamUserGroupModels);
IQueryable<string> mamUserGroups = mamUserGroupModelsOfAllUsers
.Select(mamUserGroupModel => mamUserGroupModeModel.MamUserGroup;
Or in one statement
IQueryable<string> mamUserGroups = myDbContext.Users
.SelectMany(user => user.MamRoles)
.SelectMany(mamRole => mamRole.MamUserGroupModels)
.Select(mamUserGroupModel => mamUserGroupModel.MamUserGroup);
Note that until know I haven't communicated with the database yet. I've only created the expression of the query. The query will be done once the enumeration starts:
foreach(var userGroup in mamUserGroups)
{
...
}
Note the use of SelectMany instead of Select. Whenever you have collections of collections, use SelectMany to make it one collection. If you see yourself making a foreach within a foreach, this is a good indication that a SelectMany probably would have been a better choice.
Finally: did you see that because of proper use of plurals and singulars the queries are much more readable. Less changes of making mistakes when changes have to be implemented by someone else in future.
I'm in the process of converting a project from NHibernate to Entity Framework 6.
Given this simple model:
public class User
{
public int ID { get; set; }
public string FullName { get; set; }
public virtual Organization Organization { get; set; }
// [...]
}
public class Organization
{
public int ID { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
// [...]
}
Accessing the primary key (ID) through the Organization navigation property will cause the whole Organization entity to be loaded into the context:
foreach(var user in db.Users)
Console.WriteLine(user.Organization.ID);
Given that the OrganizationID foreign key is part of the User row, I should be able to access it without causing a Lazy Load of the whole entity (and indeed, NHibernate does this properly).
Short of adding properties for the foreign key IDs into all of my 100+ entities so I can access their values without loading the entities, is there anything to be done to avoid this behaviour?
EDIT: Furthermore, even doing a null check will cause a load of the Organization entity (not in NHibernate):
foreach(var user in db.Users)
Console.WriteLine(user.Organization != null);
I guess this is due to the fundamental differences in the way the entity proxy is implemented in these two frameworks. So I'll have to adapt all of my code to this new frustrating behaviour... Unless someone already had to go through this and could enlighten me?
Nope, you'll need to add them as property in your class (that is; if you want it strong typed) like this to access it directly.
public class User
{
public int ID { get; set; }
public string FullName { get; set; }
//added ID
public int OrganizationID { get; set; }
public virtual Organization Organization { get; set; }
// [...]
}
By accessing the int you'll prevent the lazy loading, EF will bind the ID through naming conventions. Having said that: 100+ classes... :|
UPDATE:
As I just realized; you might want to try:
db.Users
.Include("Organization.ID")
.Where(/*your stuff*/) //etc.;
I am not certain if it will fully load the nested property. If it doesn't, it might be a small performance gain.
Let's say I have 3 tables:
[Table("Comments")]
public class Comment {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Users")]
public int UserId { get; set; }
public virtual Users Users { get; set; }
public string Text { get; set; }
}
[Table("Users")]
public class Users {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserName { get; set; }
}
[Table("CommentAgree")]
public class CommentAgree {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CommentId { get; set; }
public int UserId { get; set; }
}
Users post comments and other users can 'agree' with that comment, a bit like Facebook's 'like' system. I'm using lambda for my queries and I can do:
var query = db.Comments.Select(c => new {
c.Id,
c.Users.UserName,
c.Text
});
How can I create a join to CommentAgree on Comment.Id = CommentAgree.CommentId? I could write the join in Lambda but I need it to be a left join as nobody may agree with the comment but I still want it to display.
I want to do it the right way, so I'm open to suggestions whether to do it by foreign keys, lambda joins, navigation properties... or something else?
Is this possible?
Thanks
The best approach is probably to use the features of Entity Framework and create navigation properties rather than explicitly using LINQ to perform the joins just for related data.
If your types are shaped just for the purposes of data access, then adding navigation properties to both ends of the relationship is probably a good idea, along with the foreign key properties that you already have.
The collection navigation property on Comment should implement ICollection (for example List<CommentAgree>), and you would have a reference navigation property of type Comment on the CommentAgree type.
You would then have to define the relationships in your mappings, either using data annotations or (preferably) the fluent API.
To load the related data, you could either use lazy loading, or eager loading (using the Include extension method), or use explicit loading from the entry information for the entity.