EF 6: Include not building navigation properties - c#

I cant seem to figure out why my navigation property is not getting built by my include statement.
Here is my method:
public async Task<IHttpActionResult> GetCompanies(string id)
{
DbContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
var company = await DbContext.Companies.Where(x => x.Id.ToString() == id).Include(x => x.StartelAccounts).FirstOrDefaultAsync();
if (company != null)
{
return Ok(this.TheModelFactory.Create(company));
}
return NotFound();
}
When I test the SQL from the debug log I get all the fields and values for both objects.
Here are the models:
public class CompanyGroup
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime FirstBillingDate { get; set; }
[Required]
public int TermLength { get; set; }
public virtual ICollection<ApplicationUser> Members { get; set; }
public virtual ICollection<AccountStartel> StartelAccounts { get; set; }
public CompanyGroup()
{
Members = new HashSet<ApplicationUser>();
StartelAccounts = new HashSet<AccountStartel>();
}
}
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public virtual CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
Company = new CompanyGroup();
CompanyId = Guid.Empty;
UsageReports = new List<UsageReport>();
}
}
EF Fluent API
modelBuilder.Entity<AccountStartel>()
.HasRequired<CompanyGroup>(x => x.Company)
.WithMany(x => x.StartelAccounts)
.HasForeignKey(x => x.CompanyId);
modelBuilder.Entity<AccountStartel>()
.Property(p => p.DbId)
.IsRequired()
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(
new System.ComponentModel.DataAnnotations.Schema.IndexAttribute("IX_StartelDbId", 1) { IsUnique = true }));
Can anyone see what im missing here?

Could it have to do with setting Company and/or CompanyId in the
AccountStartel constructor? Does it work if you remove those lines? –
Peter
Initializing the navigation properties to a default value caused EF to not load them correctly.
Here is the updated model which does work now
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
UsageReports = new List<UsageReport>();
}
}

Related

How to properly map Entity Framework entities to DTO and vice-versa?

In the data access level, I have defined such an entity:
public class Instagram
{
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsActive { get; set; }
public bool IsSelected { get; set; }
public long UserId { get; set; }
public User User { get; set; } = null!;
public Proxy? Proxy { get; set; }
public int? ProxyId { get; set; }
public List<Work>? Works { get; set; }
}
At the abstraction level, there is such a DTO:
public class InstagramDto
{
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsSelected { get; set; }
public bool IsActive { get; set; }
public UserDto? User { get; set; }
public ProxyDto? Proxy { get; set; }
}
Did I form the DTO correctly? It maps well to one side (from EF to DTO), but the problem is reverse mapping. I do it like this:
CreateMap<InstagramDto, Instagram>()
.ForMember(x => x.UserId,
expression => expression.MapFrom((dto, _) => dto.User?.Id))
.ForMember(x => x.User, expression => expression.Ignore())
.ForMember(x => x.Proxy,
expression => expression.MapFrom((dto, _) => dto.Proxy?.Id))
.ForMember(x => x.Proxy, expression => expression.Ignore());
CreateMap<Instagram, InstagramDto>();
That is, InstagramDto must have UserDTO and ProxyDto so that I can correctly map the entity from DTO to ef. At the same time, the user may have some other navigation properties that are not involved when receiving Instagram. This means that I cannot update the User, as its navigation properties are not loaded in this situation. Is this the right approach or would it be better to do so:
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsSelected { get; set; }
public bool IsActive { get; set; }
public long UserId { get; set; }
public int ProxyId { get; set; }

Entity Framework Core - Finding data in another table using a lambda function

I have two tables. Account and Tenant. There are many accounts to a tenant and this has been configured in the DbContext as follows.
modelBuilder.Entity<Account>()
.HasOne(b => b.Tenant)
.WithMany(a => a.Accounts)
.OnDelete(DeleteBehavior.Cascade);
The Account POCO class is as follows.
public class Account : IEntityBase, IAuditedEntityBase
{
public int Id { get; set; }
public int AccountNo { get; set; }
public string? AccountName { get; set; }
public string? Title { get; set; }
public string? AccountFirstName { get; set; }
public string? AccountLastName { get; set; }
public string? MobilePhone { get; set; }
public string? Email { get; set; }
public string? Address1 { get; set; }
public string? Address2 { get; set; }
public string? PasswordHash { get; set; }
public bool AcceptTerms { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
public string? VerificationToken { get; set; }
public DateTime? Verified { get; set; }
public bool IsVerified => Verified.HasValue || PasswordReset.HasValue;
public string? ResetToken { get; set; }
public DateTime? ResetTokenExpires { get; set; }
public DateTime? PasswordReset { get; set; }
public List<RefreshToken>? RefreshTokens { get; set; }
public bool OwnsToken(string token)
{
return this.RefreshTokens?.Find(x => x.Token == token) != null;
}
// One tenant to many user accounts
public int TenantId { get; set; }
public virtual Tenant? Tenant { get; set; }
// One suburb to many User accounts
public int SuburbId { get; set; }
public virtual Suburb? Suburb { get; set; }
}
The Tenant POCO class is as follows:
public class Tenant : IEntityBase, IAuditedEntityBase
{
public Tenant()
{
Accounts = new List<Account>();
}
public int Id { get; set; }
public int TenantNo { get; set; }
public string Database { get; set; }
public string CompanyName { get; set; }
public string ABN { get; set; }
public string CompanyAccountEmail { get; set; }
public string ContactFirstName { get; set; }
public string ContactLastName { get; set; }
public string OfficePhone { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string BankName { get; set; }
public string BankBSB { get; set; }
public string BankAccount { get; set; }
public int SuburbId { get; set; }
public virtual Suburb Suburb { get; set; }
// Many users to one tenant
public virtual ICollection<Account> Accounts { get; }
}
N ogiven there are multiple accounts to a tenant or "many accounts to one tenant" how,
if I have the account ID, obtain the tenantId using a lambda function.
I tried to use the following but got lost.
await tenantsContext.Accounts.Include(x => x.Tenant).Where(x => x.Id == accountId).SingleOrDefaultAsync(x => new Tenant.. and lost it here..
Can someone show me and others how you would, given an accountId (which equates to the Id of the account table) and get the TenantId for that account..
You can get TenantId for account with given accountId using this:
await tenantsContext.Accounts
.Where(x => x.Id == accountId)
.Select(x => x.TenantId)
.SingleOrDefaultAsync();

Entity Framework Core not loading related data from reference table

MY Model M:M relationship Reference to
https://www.entityframeworktutorial.net/efcore/configure-many-to-many-relationship-in-ef-core.aspx
Models
public class Post
{
[Key]
public int Id { get; set; }
[Display(Name = "Created By:")]
public AppUser AuthorId { get; set; }
[Required]
public string Title { get; set; }
public string metaTitle { get; set; }
[Required]
public string Body { get; set; }
public bool Published { get; set; }
public bool ISFeatured { get; set; }
public DateTime CretedDate { get; set; } = DateTime.Now;
public DateTime ModifiyDate { get; set; } = DateTime.Now;
public IList<Comment> Comments { get; set; }
public IList<PostTag> PostTag { get; set; }
public IList<PostCategory> PostCategory { get; set; }
public IList<Images> Images { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public bool Published { get; set; } = true;
public DateTime CretedDate { get; set; } = DateTime.Now;
public DateTime ModifiyDate { get; set; } = DateTime.Now;
public IList<PostTag> PostTag { get; set; }
public IList<Images> Images { get; set; }
}
public class PostTag
{
public int TagId { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public Tag Tag { get; set; }
public AppUser AppUser { get; set; }
}
DB context
modelBuilder.Entity<Post>()
.HasMany(c => c.Comments)
.WithOne(e => e.Post);
modelBuilder.Entity<PostCategory>().HasKey(p => new
{
p.PostId,p.CategoryId
});
modelBuilder.Entity<PostCategory>()
.HasOne(p => p.post).
WithMany(p => p.PostCategory).
HasForeignKey(p => p.PostId);
modelBuilder.Entity<PostCategory>().
HasOne(p => p.Category).
WithMany(p => p.PostCategory).
HasForeignKey(p => p.CategoryId);
On the controller, side fetching all posts, it is bringing all the posts but not getting any data from the related tables. Example Tags, Categories
Controller
public async Task<IActionResult> Index()
{
return View(await _context.Post.ToListAsync());
}
Update Action
Tags reference is empty
Use ThenInclude to continue including further levels of related data.
var posts = _context.Posts.Include(p => p.PostTag).ThenInclude(pt => pt.Tag).ToList();
try _context.Post.Include(x => x.PostCategory) and so on.
Reference: https://learn.microsoft.com/en-us/ef/core/querying/related-data

EF Query getting the Include operation for navigation not reachable warning

I have a simple query
clients = _context.Clients
.Include(e => e.TrainerClients)
.Include(e => e.User)
.Where(e => e.TrainerClients.All(f => f.Status == UserStatus.Linked));
that returns:
but I get a warning message if executed as-is:
The Include operation for navigation '[e].User' is unnecessary and was ignored because the navigation is not reachable in the final query results.
However, if I comment out the Include(e => e.User) from the query, I don't get the warning, but I also don't get the User data.
So why am I getting this warning, and how can I refine my query to not get the warning?
UPDATE:
Client model:
public class Client : BaseModel
{
public string UserId { get; set; }
[ForeignKey(nameof(UserId))]
public ApplicationUser User { get; set; }
public DateTime LastPaidDate { get; set; }
public string CreditCardNumber { get; set; }
public ICollection<TrainerClient> TrainerClients { get; set; }
}
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public bool Facebook { get; set; }
public string ProfilePicture { get; set; }
public string CountryCode { get; set; }
public string State { get; set; }
public string City { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual DateTime? LastLoginTime { get; set; }
public virtual DateTime? RegistrationDate { get; set; }
public string Address { get; set; }
public string ZipCode { get; set; }
public int StateId { get; set; }
//Properties Client used just for relation
public Client Client { get; set; }
public ProfileSetting ProfileSetting { get; set; }
}
TrainerClient:
public class TrainerClient : BaseModel
{
public int TrainerId { get; set; }
[ForeignKey(nameof(TrainerId))]
public Trainer Trainer { get; set; }
public int ClientId { get; set; }
[ForeignKey(nameof(ClientId))]
public Client Client { get; set; }
public Enum.UserStatus Status { get; set; }
public Enum.RequestType RequestType { get; set; }
public string RequestDate { get; set; }
}

Access Entity Framework autogenerated FK column

I have two classes, where Plan has a list of PlanTask, like below. However when it was initially made, there was not made a Property for the Foreign Key to go from PlanTask to Plan. However now i need the ID of Plan on PlanTask. My problem lies in because of the on to many relationship that exists between these two, Entity Framework created a Foreign Key column. However as i have no property to access that column, i can't access the id. So my question is, can i access the autogenerated column somehow, or can i edit my classes so that i don't delete the data that already exists.
Plan:
public class Plan
{
public int PlanId { get; set; }
[DisplayName("Navn")]
public string Name { get; set; }
public IList<PlanTask> Tasks { get; set; }
public bool IsActive { get; set; }
public ICollection<Department> Departments { get; set; }
public bool IsCompleted { get; set; }
public PlanStages Stage { get; set; }
public DateTime? ApprovedDate { get; set; }
[DisplayName("Ejer")]
public User Owner { get; set; }
[DisplayName("Ejer e-mail")]
public string OwnerMail { get; set; }
}
PlanTask:
public class PlanTask
{
public int PlanTaskId { get; set; }
public bool IsActive { get; set; }
[DisplayName("Beskrivelse/Årsag")]
[Required(ErrorMessage = "Indtast venligst en beskrivelse/årsag")]
public string Description { get; set; }
[DisplayName("Løsning")]
public string Solution { get; set; }
[DisplayName("Ansvarlig e-Mail")]
[Required(ErrorMessage = "Indtast venligst den ansvarliges mail")]
public string ManagerMail { get; set; }
[DisplayName("Ansvarlig navn")]
[Required(ErrorMessage = "Indtast venligst den ansvarliges navn")]
public string ManagerName { get; set; }
[DisplayName("Videresend til navn")]
public string ForwardName { get; set; }
[DisplayName("Videresend til e-Mail")]
public string ForwardMail { get; set; }
[DisplayName("Dato udført")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? DateCompleted { get; set; }
[DisplayName("Deadline for udførelse")]
public DateTime? Deadline { get; set; }
[DisplayName("Dato der er fulgt op")]
public DateTime? FollowupDate { get; set; }
[DisplayName("Påmindelses dato")]
public DateTime? ReminderDate { get; set; }
[DisplayName("Vedhæft fil")]
public string FileName { get; set; }
}
DataContext:
public class DataContext : IdentityDbContext<User>
{
public DataContext() : base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("AspNetUsers");
modelBuilder.Entity<IdentityUserRole>().ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");
modelBuilder.Entity<IdentityRole>().ToTable("AspNetRoles");
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
modelBuilder.Entity<User>()
.HasMany(x => x.Departments)
.WithMany(c => c.Users);
modelBuilder.Entity<Department>()
.HasMany(x => x.Users)
.WithMany(c => c.Departments);
base.OnModelCreating(modelBuilder);
}
public static DataContext Create()
{
return new DataContext();
}
public DbSet<Template> Templates { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<Question> Questions { get; set; }
public DbSet<SubmittedSheet> SubmittedSheets { get; set; }
public DbSet<SubmittedQuestion> SubmittedQuestions { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Role> WorkRoles { get; set; }
public DbSet<Actionplan> Plans { get; set; }
public DbSet<ActionPlanTask> PlanTasks { get; set; }
//public DbSet<User> Users { get; set; }
}
or can i edit my classes so that i don't delete the data that already exists.
Yes. Just add
public Plan Plan {get; set; }
[Column("Plan_PlanId")]
[ForeignKey("Plan")]
public int PlanId { get; set; }
and make sure it exactly matches the name of the generated column. You can use attributes for that.
Test it with a little care but when you do it right and then do an add-migration that new migration should be empty.

Categories