C# ASP.NET Core AutoMapper: Child object is null - c#

I have a many to many relationship between Users and Accounts.
When I do an HttpGet, I want to return AccountForDetailDto which includes an ICollection<UserForListDto> to list the users attached to that account.
I get the User IDs of the users attached to the account, but the User objects are null.
I assume that I need to do an Include somewhere, but I'm not sure where.I've tried .Include(account => account.UserAccounts.Select(userAccounts => userAccounts.User).
Or am I missing a map/property of a map.
Here's what I've got:
AccountsController.cs
[HttpGet("{id}", Name = "GetAccount")]
public async Task<IActionResult> GetAccount(int id)
{
var accountFromRepo = await _repo.GetAccount(id);
var accountToReturn = _mapper.Map<AccountForDetailedDto>(accountFromRepo);
return Ok(accountToReturn);
}
AccountRepository.cs
public async Task<Account> GetAccount(int id)
{
return await _context.Accounts
.Include(a => a.UserAccounts)
.FirstOrDefaultAsync(a => a.Id == id);
}
AutoMapperProfiles.cs
CreateMap<UserAccount, UserForListDto>();
CreateMap<Account, AccountForDetailedDto>()
.ForMember(dto => dto.Users, opt => opt.MapFrom(a => a.UserAccounts));
Models
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<UserAccount> UserAccounts { get; set; }
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<UserAccount> UserAccounts { get; set; }
}
public class UserAccount
{
public int? UserId { get; set; }
public User User { get; set; }
public int? AccountId { get; set; }
public Account Account { get; set; }
}
Dtos
public class AccountForDetailedDto
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<UserForListDto> Users { get; set; }
}
public class UserForListDto
{
public int Id { get; set; }
public string Username { get; set; }
}

Related

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

Adding data to crosstable created by EF with AutoMapper

It is my first a many-to-many relation consisting of Team, User and TeamUser objects. In TeamController I mapped TeamForCreationDto to Team, but ICollection Members was empty. Some bug in CreateMap?Q1: How it should be combined to fill all property and tables by EF? Now I have "for" loop and there created/added TeamUser.
Q2: If I must fill both property AdminId and Admin?
A2: No, after adding Admin, property AdminId in DB thanks EF will find value automatically.
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public int AdminId { get; set; }
public User Admin { get; set; }
//public int[] MembersId { get; set; }
public ICollection<TeamUser> Members { get; set; }
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Team> TeamsAsAdmin { get; set; }
public ICollection<TeamUser> TeamsAsMember { get; set; }
}
public class TeamUser
{
public int TeamId { get; set; }
public Team Team { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Relations between tables in ModelBuilder
builder.Entity<Team>()
.HasOne(t => t.Admin)
.WithMany(u => u.TeamsAsAdmin)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<TeamUser>()
.HasKey(tu => new { tu.TeamId, tu.UserId });
builder.Entity<TeamUser>()
.HasOne(tu => tu.User)
.WithMany(u => u.TeamsAsMember)
.HasForeignKey(tu => tu.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<TeamUser>()
.HasOne(tu => tu.Team)
.WithMany(t => t.Members)
.HasForeignKey(tu => tu.TeamId);
My CreateMap in AutoMapperProfiles()
CreateMap<TeamForCreationDto, Team>().ReverseMap().ForMember(u => u.MembersId, opt => opt.MapFrom(x => x.Members));
My TeamController.cs
public async Task<IActionResult> Create(int userId, TeamForCreationDto teamForCreationDto)
{
if (await _repoTeams.TeamExists(teamForCreationDto.Name))
return BadRequest("A team with this name already exists!");
var mappedTeam = _mapper.Map<Team>(teamForCreationDto);
//mappedTeam.AdminId = userId;
mappedTeam.Admin = await _repoUsers.GetUser(userId);
_repoTeams.Add(mappedTeam);
for (int i = 0; i < teamForCreationDto.MembersId.Length; i++)
{
TeamUser tm = new TeamUser();
tm.Team = mappedTeam;
tm.User = await _repoUsers.GetUser(teamForCreationDto.MembersId[i]);
_repoTeams.Add(tm);
}
await _repoTeams.SaveAll();
}
TeamForCreationDto.cs
public class TeamForCreationDto
{
int Id { get; set; }
public string Name { get; set; }
public string PhotoUrl { get; set; }
public int[] MembersId { get; set; }
}

add ICollection User in the same entity User

Whether it is possible so: it is a Messenger where the entity User content ICollection User that are collection Friends consist from the same Users?
If that possible please tell me how create a correct relationship between them in the DbContext file?
Or how better build this relationship. May be create separate entity?
Thanks in advance!
namespace Tinkl.Data.Core.Domain
{
public class User
{
public User()
{
Contacts = new List<User>();
Conversations = new List<Conversation>();
Invites = new List<User>();
}
public int UserId { get; set; }
public string NickName { get; set; }
public string EMail { get; set; }
public string Password { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? ExitDate { get; set; }
public string Picture { get; set; }
public string Status { get; set; }
public virtual ICollection<User> Invites { get; set; }
public virtual ICollection<User> Contacts { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
}
}
You are going in right direction, see my below code same type of self-relationship in EF code first
public class ContentEntityRef : BaseModel
{
public ContentEntityRef()
{
RoleRefs = new HashSet<RoleRef>();
}
public int EntityId { get; set; }
public string EntityName { get; set; }
public int? ParentEntityId { get; set; }
public virtual ICollection<RoleRef> RoleRefs { get; set; }
public virtual ContentEntityRef Parent { get; set; }
public virtual ICollection<ContentEntityRef> Children { get; set; }
}
I had created seprate configuration file, you can same use in dbContext "OnModelCreating" method.
internal class ContentEntityRefConfiguration : EntityTypeConfiguration<ContentEntityRef>, IEntityConfiguration
{
public ContentEntityRefConfiguration()
{
this.HasKey(x => x.EntityId).Property(t => t.EntityId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.EntityName).IsRequired().HasMaxLength(50);
this.HasMany(c => c.Children).WithOptional(c => c.Parent).HasForeignKey(c => c.ParentEntityId);
this.HasMany<RoleRef>(role => role.RoleRefs)
.WithMany(content => content.ContentEntities)
.Map(contentRole =>
{
contentRole.MapLeftKey("EntityID");
contentRole.MapRightKey("RoleID");
contentRole.ToTable("RoleEntityMap");
});
}
}
hope this will help you :)

Entity Framework 7 Accessing Entity Objects inside ICollection

I have two entities Users and Addresses. Addresses is an ICollection property on the Users entities. However i am not able to access the individual addresses inside the ICollection. Is that possible and i just have the wrong syntax or does entity framework not allow that.
The Code:
Users:
public partial class User
{
public User()
{
Addresses = new HashSet<Address>();
Comments = new HashSet<Comment>();
Orders = new HashSet<Order>();
Posts = new HashSet<Post>();
}
public long Id { get; set; }
public bool Active { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime Created { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? Updated { get; set; }
public string Username { get; set; }
public long? UserTypeId { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public virtual UserType UserType { get; set; }
}
Addresses:
public partial class Address
{
public long Id { get; set; }
public bool Active { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public DateTime Created { get; set; }
public string State { get; set; }
public DateTime? Updated { get; set; }
public long? UserId { get; set; }
public string Zip { get; set; }
public virtual User User { get; set; }
}
Repository (Parent):
public class Repository<T> where T : class
{
private DemoContext _context;
protected DbSet<T> DbSet;
public Repository(DemoContext context)
{
_context =context;
DbSet = _context.Set<T>();
}
public virtual T Get(int Id)
{
// TODO: Implement with return of DbSet.Find();
// DbSet.Find(Id);
return null;
}
public virtual List<T> GetAll()
{
return DbSet.ToList();
}
public virtual void Add(T entity)
{
DbSet.Add(entity);
}
public virtual void Update(T user)
{
_context.Entry<T>(user)
.State = EntityState.Modified;
}
public virtual void SaveChanges()
{
_context.SaveChanges();
}
public virtual void Delete(int Id)
{
// TODO: Implement with return of DbSet.Find();
// DbSet.Remove(Dbset.Find(Id));
}
UserRepository:
public class UserRepository : Repository<User>
{
public UserRepository(DemoContext context)
: base(context)
{
}
public override User Get(int Id)
{
return DbSet
.Where(o => o.Id == Id)
.Include(o => o.Orders)
.Include(o => o.Addresses)
.ToList()
.Single();
}
public override List<User> GetAll()
{
return DbSet
.Include(o => o.Orders)
.Include(o => o.Addresses)
.ToList();
}
public override void Delete(int Id)
{
DbSet.Remove(Get(Id));
}
}
The following code will not give me access to the first address in the ICollection property on the user entity
[HttpGet("[action]")]
public IActionResult Create()
{
var user = userRepository.Get(1);
var order = new Order
{
Address = user.Addresses[0].Address,
City = user.Addresses[0].City,
State = user.Addresses[0].State,
Zip = user.Addresses[0].Zip,
User = user,
SubTotal = 100,
Tax = 25,
Total = 125
};
orderRepository.Add(order);
orderRepository.SaveChanges();
return RedirectToAction("Index");
}
Please advise on how to correct my code so i have access to the entities in the collection.
Just in case anyone was wondering how i solved this here is the answer:
As mentiond by COLD TOLD ICollection does not support array like indexing. However Lists do so change it to a List instead of an ICollection and deleted the hashset that was created in the custructor
public partial class User
{
public User()
{
}
public long Id { get; set; }
public bool Active { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime Created { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? Updated { get; set; }
public string Username { get; set; }
public long? UserTypeId { get; set; }
public virtual List<Address> Addresses { get; set; }
public virtual List<Comment> Comments { get; set; }
public virtual List<Order> Orders { get; set; }
public virtual List<Post> Posts { get; set; }
public virtual UserType UserType { get; set; }
}
This allows me to access those nested entities as like this:
[HttpGet("/")]
public IActionResult Create()
{
var user = userRepository.Get(1);
var order = new Order
{
Address = user.Addresses[0].Address1,
City = user.Addresses[0].City,
State = user.Addresses[0].State,
Zip = user.Addresses[0].Zip,
User = user,
SubTotal = 100,
Tax = 25,
Total = 125
};
orderRepository.Add(order);
orderRepository.SaveChanges();
return RedirectToAction("Index");
}
I do not think it a good idea to have that many includes in your select it might cause problems in your performance , you might try to change the order of the query
return DbSet.Include(o => o.Orders)
.Include(o => o.Addresses)
.Where(o => o.Id == Id)
.FirstOrDefault();

EF7 nested Include not showing in Razor .net

I have my models like this:
Goup.cs
GroupUser (pivot table)
ApplicationUser (User) -> 4. Profile
And now I want to show the data in Profile on a details page when the User belongs to the group. I'm doing this like this:
private IEnumerable<GroupUser> GetUsers(int groupId)
{
IEnumerable<GroupUser> model = null;
if(groupId == 0)
{
model = _kletsContext.GroupUser.OrderByDescending(o => o.GroupId).AsEnumerable();
}
else
{
model = _kletsContext.GroupUser.Where(g => g.GroupId == groupId).Include(p => p.User.Profile).OrderByDescending(o => o.GroupId).AsEnumerable();
}
return model;
}
This works, if I just want to display the UserId, ... (so the data in the Pivot table) with this code:
#model IEnumerable<App.Models.GroupUser>
#if(Model != null && Model.Count() > 0)
{
#foreach(var user in Model)
{
#user.UserId</h2>
}
}
But for some reason I can't display the data in the Included tables?
Normally you would do something like this: #user.User.Profile.XXXX but then I get the error: System.NullReferenceException: Object reference not set to an instance of an object
So this would mean the return is null, but there are users in the pivot table with a profile.
The models:
Group.cs:
namespace App.Models
{
public class Group : Item
{
public Group() : base()
{
}
[Key]
public Int16 Id { get; set; }
public string Name { get; set; }
public string Images { get; set; }
/* Foreign Keys */
public Nullable<Int16> RegionId { get; set; }
public virtual Region Region { get; set; }
public virtual ICollection<Lets> Lets { get; set; }
public virtual ICollection<GroupUser> Users { get; set; }
}
}
ApplicationUser:
namespace App.Models.Identity
{
public class ApplicationUser : IdentityUser
{
public string Description { get; set; }
public DateTime CreatedAt { get; set; }
public Nullable<DateTime> UpdatedAt { get; set; }
public Nullable<DateTime> DeletedAt { get; set; }
/* Virtual or Navigation Properties */
public virtual Profile Profile { get; set; }
public virtual ICollection<GroupUser> Groups { get; set; }
public virtual ICollection<Lets> Lets { get; set; }
public virtual ICollection<Category> Categories { get; set; }
public virtual ICollection<Region> Regions { get; set; }
public virtual ICollection<Status> Status { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
}
GroupUser:
namespace App.Models
{
public class GroupUser
{
public GroupUser()
{
}
public Nullable<Int16> GroupId { get; set; }
public string UserId { get; set; }
public virtual Group Group { get; set; }
public virtual ApplicationUser User { get; set; }
}
}
Profile.cs:
namespace App.Models
{
public class Profile : Item
{
public Profile() : base()
{
}
[Key]
public string UserId { get; set; }
public string FirstName { get; set; }
public string SurName { get; set; }
public string Email { get; set; }
public string Gender { get; set; }
public Int16 Age { get; set; }
public string City { get; set; }
public string Image { get; set; }
public Int16 Credits { get; set; }
public Int16 Postalcode { get; set; }
}
}
How can i display the nested data with razor?
model = _kletsContext.GroupUser.Where(g => g.GroupId == groupId)
.Include(gu => gu.User)
.ThenInclude(u => u.Profile)
.OrderByDescending(o => o.GroupId)
.AsEnumerable();
Don't get freaked out when intellisense doesn't work for the ThenInclude, just type it, it will compile.
try to include the user-reference
model = _kletsContext.GroupUser.Where(g => g.GroupId == groupId).Include(p => p.User).Include(p => p.User.Profile).OrderByDescending(o => o.GroupId).AsEnumerable();

Categories