Prevent circular references when getting result from database - c#

If I have the following simple model:
public class Company
{
public Guid CompanyId { get; set; }
public ICollection<CompanyUser> Users { get; set; }
}
public class CompanyUser
{
public Guid CompanyId { get; set; }
public Guid UserId { get; set; }
public Company Company { get; set; }
public User User { get; set; }
}
public class User
{
public Guid UserId { get; set; }
public ICollection<CompanyUser> Companies { get; set; }
}
To get a list of companies + their users + the user object I run the following query:
return await _dataContext.Companies
.Include(m => m.Users)
.ThenInclude(m => m.User)
.OrderBy(m => m.Name)
.ToListAsync();
The results work, but I use a mapper to map the results to a view model by going recurisvely through the model.
What happens is that the Company object has a reference to a list of CompanyUser, in each of those CompanyUser objects we have a Company which has a list of CompanyUser again, which just keeps repeating until we get a stack overflow.
The mapper is a very simple one:
var results = companies.ToViewModel<Company, CompanyViewModel>();
public static IList<TModel> ToViewModel<TEntity, TModel>(this IEnumerable<TEntity> entities)
where TEntity : class
where TModel : class, IViewModel<TEntity>, new()
{
return entities?.Select(entity => entity.ToViewModel<TEntity, TModel>()).ToList();
}
public static TModel ToViewModel<TEntity, TModel>(this TEntity entity)
where TEntity : class
where TModel : class, IViewModel<TEntity>, new()
{
if (entity == null)
{
return null;
}
var model = new TModel();
model.ToViewModel(entity);
return model;
}
public interface IViewModel<in TEntity>
where TEntity : class
{
void ToViewModel(TEntity entity);
}
public class CompanyViewModel : IViewModel<Company>
{
public Guid CompanyId { get; set; }
public IList<CompanyUserViewModel> Users { get; set; }
public void ToViewModel(Company entity)
{
CompanyId = entity.CompanyId;
Users = entity.Users.ToViewModel<CompanyUser, CompanyUserViewModel>();
}
}
public class CompanyUserViewModel : IViewModel<CompanyUser>
{
public Guid CompanyId { get; set; }
public Guid UserId { get; set; }
public CompanyViewModel Company { get; set; }
public UserViewModel User { get; set; }
public void ToViewModel(CompanyUser entity)
{
CompanyId = entity.CompanyId;
UserId = entity.UserId;
Company = entity.Company.ToViewModel<Company, CompanyViewModel>();
User = entity.User.ToViewModel<User, UserViewModel>();
}
}
public class UserViewModel : IViewModel<User>
{
public Guid UserId { get; set; }
public void ToViewModel(User entity)
{
UserId = entity.Id;
}
}
Is there a way to prevent these references to be resolved?

There are multiple solutions:
1) You can use automapper instead of own mapper. It has MaxDepth property which will prevents from this problem:
Mapper.CreateMap<Source, Destination>().MaxDepth(1);
2) You can remove dependencies from your entities and use shadow properties in one direction.

Are you open to changing your data model? I think the best solution would be to remove the circular reference.
If a company contains a list of users, does the User also need both the CompanyId and the Company object he is contained in? I would remove public Company Company { get; set; } from your CompanyUser object and Companies from your User object.

Your issue is that you are mapping to CompanyViewModel which then maps to CompanyUserViewModel but this then maps back again to CompanyViewModel which creates an infinite loop.
If you expect to always start at Company (to CompanyView) then remove the recursion back from CompanyUserViewModel.
public void ToViewModel(CompanyUser entity)
{
CompanyId = entity.CompanyId;
UserId = entity.UserId;
// Company = entity.Company.ToViewModel<Company, CompanyViewModel>();
User = entity.User.ToViewModel<User, UserViewModel>();
}
Alternatively do not map the relations in your ToViewModel mapping, wire up the relations after based on the Ids.

Related

Why does my IdentityUser ICollection property not load on ASP.NET Core 5.0?

I have a many-to-many relationship between AppUser : IdentityUser, and TaxAccount, which is an abstract class that separates into Household and Business. I want to query how many TaxAccounts a User has access to on load.
Here's the AppUser class.
public class AppUser : IdentityUser
{
public string FullName { get; set; }
public virtual List<TaxAccount> Accounts { get; set; }
}
Here's the TaxAccount classes.
public abstract class TaxAccount
{
[Key]
public int ID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public virtual List<AppUser> Users { get; set; }
}
public class Household : TaxAccount
{
[MaxLength(1000)]
public string HomeAddress { get; set; }
}
public class Business : TaxAccount
{
[EmailAddress, MaxLength(500)]
public string Email { get; set; }
}
However, when I attempt to query the AppUser object in my Razor page, AppUser.Accounts is null! As a result, I always return to the "NoAccount" page.
public async Task<IActionResult> OnGetAsync()
{
AppUser = await _manager.GetUserAsync(User);
// Checks how many TaxAccounts the user has.
if (AppUser.Accounts == null)
{
return RedirectToPage("NoAccount");
}
else if (AppUser.Accounts.Count == 1)
{
return RedirectToPage("Account", new { accountID = AppUser.Accounts[0].ID });
}
else
{
return Page();
}
}
I've found that if I use a .Include() statement to manually connect TaxAccounts to AppUsers, the connection sticks! I just have to insert a .ToList() call that goes nowhere. My question is: why do I need this statement?
AppUser = await _manager.GetUserAsync(User);
_context.Users.Include(X => X.Accounts).ToList();
// Now this works!
if (AppUser.Accounts == null)
EDIT: I've tested removing virtual to see if it was a lazy loading thing, there's no difference.

EF Core .ThenInclude does not include foreign entity and causes query to retrieve nothing

I have an EF Core project here, but I do have difficulties with multilevel includes. I'm trying to query entries which do relate like this:
There's a mapping table for "friend" relationships from accountid to accountid. So layer one is this mapping entity.
The IDs of the accounts in the mapping table are foreign keys relating to the respective Account entity.
Within the account entity, there's a foreign key to an account online state entity.
So tl;dr; FriendsMappingTable -> Account -> AccountOnlineState.
Here's the code I do use:
public Task<List<TEntity>> Read(Expression<Func<TEntity, bool>> predicate, params Func<IQueryable<TEntity>, IQueryable<TEntity>>[] foreignIncludes)
{
return RunInContextWithResult(async dbSet =>
{
var query = dbSet.Where(predicate);
query = foreignIncludes.Aggregate(query, (current, include) => include(current));
return await query.ToListAsync();
});
}
private async Task<List<TEntity>> RunInContextWithResult([NotNull] Func<DbSet<TEntity>, Task<List<TEntity>>> dbFunc)
{
await using var ctx = GetContext();
return await dbFunc(ctx.Set<TEntity>());
}
and here's my call to that:
var friends = await m_friendsMappingRepository.Read(
x => x.Id == sessionContext.Account.Id,
x => x.Include(y => y.Friend).ThenInclude(y => y.AccountOnlineStateEntity));
However, with this setup, the query will just return nothing at all. If I remove the .ThenInclude(), it will at least return a corresponding friend entity for the given account, with the OnlineState entity set to null.
Here are the (stripped down) entities:
public interface IEntity<TKeyType>
{
[NotNull]
[Key]
[Column("Id")]
public TKeyType Id { get; set; }
}
[Table("FriendsMapping")]
public class FriendsMappingEntity : IEntity<int>
{
[ForeignKey("Account")]
public int Id { get; set; }
public AccountEntity Account {
get;
[UsedImplicitly] private set;
}
[Column("FriendId")]
[ForeignKey("Friend")]
public int FriendId { get; set; }
public AccountEntity Friend
{
get;
[UsedImplicitly] private set;
}
}
public class AccountEntity : IEntity<int>
{
[ForeignKey("AccountOnlineStateEntity")]
public int Id { get; set; }
[CanBeNull]
public AccountOnlineStateEntity AccountOnlineStateEntity { get; set; }
[NotNull]
public List<FriendsMappingEntity> FriendsTo { get; set; }
[NotNull]
public List<FriendsMappingEntity> FriendsFrom { get; set; }
}
public class AccountOnlineStateEntity : IEntity<int>
{
public int Id { get; set; }
[Column("OnlineState")]
public AccountOnlineState OnlineState { get; set; }
}
Update
Building on Ivan's suggestion, add an InverseProperty and remove the ForeignKey from Account.Id.
//[ForeignKey("AccountOnlineStateEntity")]
public int Id { get; set; }
[CanBeNull]
[InverseProperty("Account")
public AccountOnlineStateEntity AccountOnlineStateEntity { get; set; }
And add a property to AccountOnlineStateEntity
[ForeignKey("Id")]
public AccountEntity Account { get; set; }

Entity Framework Generalise Id and Parent Name but have specific names like "ReferralId" and "ClientID"

I have a lot of models in my program that do similar things but we have a database practice to give each column specific names so "PersonId" instead of "Id"
But I would like to deal with them in a generic way. So for example in a class I have included below I want to do something like:
public virtual List<TModel> GetAll(int parentId)
{
return Context.Where(i => i.ParentID == parentId).ToList();
}
But I can't do that if ParentID unless its mapped to a database column
The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported
So I am now trying to map it to a database column:
public interface IGenModel
{
int Id { get; set; }
int ParentID { get; set; }
}
public class ChildNote : BaseModel, IGenModel
{
[Key]
[DisplayName("Child Note ID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ChildId")]
public int Id { get; set; }
[DisplayName("Referral")]
[Column("ReferralId")]
public int ParentID { get; set; }
[DisplayName("Cinican Notes")]
public string Notes { get; set; }
[ForeignKey("ReferralId")]
public virtual Referral Referral { get; set; }
/* Finally I tried to add something like this here
[NotMapped]
[Required]
[DisplayName("Referral")]
public int ReferralId => ParentID
*/
/* Original set uup
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DisplayName("Child Note ID")]
public int ChildId { get; set; }
[Required]
[DisplayName("Referral")]
public int ReferralId { get; set; }
public int Id { get; set; }
{
get { return ChildId; }
set { ChildId = value; }
}
public int ParentID { get; set; }
{
get { return ReferralId; }
set { ReferralId = value; }
}*/
}
But when I try and build or update the database I get errors due to:
[ForeignKey("ReferralId")]
public virtual Referral Referral { get; set; }
As it says ReferralId does not map to something in the model.
Am I fighting a losing battle here? Is is simply not possible to generalise this? And just accept that all my accesses of context may have to be specialised? I am not going to be able to change the rule that we always prefix "Id" with the model name, apparently its very useful for searches within the database which makes sense to me.
and the classes I want to use
public abstract class GenLoader<TSelf, TModel> where TModel : BaseModel, IGenModel where TSelf : class, new()
{
private static TSelf instance = null;
public static TSelf Instance
{
get
{
if (instance == null)
{
instance = new TSelf();
}
return instance;
}
}
protected abstract DbSet<TModel> Context { get; }
public virtual TModel Get(int id)
{
return Context.FirstOrDefault(i => i.Id == id);
}
public virtual List<TModel> GetAll(int parentId)
{
return Context.Where(i => i.ParentID == parentId).ToList();
}
public virtual void OnAdd(TModel model)
{
}
public virtual void Add(TModel model)
{
model.LastModifiedBy = WebSecurity.CurrentUserId;
model.LastModified = DateTime.Now;
OnAdd(model);
using (TransactionScope scope = new TransactionScope())
{
Context.Add(model);
ModelContext.Current.SaveChanges();
scope.Complete();
}
}
public virtual void OnUpdate(TModel model)
{
}
public void Update(TModel model)
{
model.LastModifiedBy = WebSecurity.CurrentUserId;
model.LastModified = DateTime.Now;
OnUpdate(model);
using (TransactionScope scope = new TransactionScope())
{
ModelContext.Current.Entry(model).State = EntityState.Modified;
ModelContext.Current.SaveChanges();
scope.Complete();
}
}
}
public class ChildNotes : GenLoader<ChildNotes, ChildNote>
{
protected override DbSet<ClinicalNote> Context => ModelContext.Current.ClinicalNotes;
}
When applied to navigation property, ForeignKey attribute expects the FK property name, not the mapped db column name. So the following should fix the issue:
[ForeignKey("ParentID")]
public virtual Referral Referral { get; set; }
The alternative is, since you put many attributes on FK property, remove the ForeignKey attribute from navigation property and apply it on the FK property, in which case it expects the navigation property name as an argument:
[DisplayName("Referral")]
[Column("ReferralId")]
[ForeignKey("Referral")]
public int ParentID { get; set; }
...
public virtual Referral Referral { get; set; }

Entity Framework navigating using navigation properties more than one level

I have Entity model classes as follows
public partial class User
{
public User()
{
this.webpages_Roles = new HashSet<webpages_Roles>();
}
public int UserID { get; set; }
public virtual ICollection<webpages_Roles> webpages_Roles { get; set; }
}
.
public partial class webpages_Roles
{
public webpages_Roles()
{
this.Users = new HashSet<User>();
this.Roles_X_ApplicationModules =
new HashSet<Roles_X_ApplicationModules>();
}
public int RoleId { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Roles_X_ApplicationModules>
Roles_X_ApplicationModules { get; set; }
}
.
public partial class Roles_X_ApplicationModules
{
public long ID { get; set; }
public Nullable<int> ModuleID { get; set; }
public Nullable<int> RoleID { get; set; }
public Nullable<bool> ViewPermission { get; set; }
public virtual ApplicationModule ApplicationModule { get; set; }
public virtual webpages_Roles webpages_Roles { get; set; }
}
.and
public partial class ApplicationModule
{
public ApplicationModule()
{
this.Roles_X_ApplicationModules =
new HashSet<Roles_X_ApplicationModules>();
}
public int ModuleID { get; set; }
public virtual ICollection<Roles_X_ApplicationModules>
Roles_X_ApplicationModules { get; set; }
}
you can see User object has a navigation property to webpages_Roles which again has navigation property to Roles_X_ApplicationModules and which in turn navigates to ApplicationModule..
now I want to get all the ApplicationModule from User..how do I write query using navigation properties..
I tried something like this..
var appModules = user.webpages_Roles.SingleOrDefault()
.Roles_X_ApplicationModules.Where(z => z.ViewPermission == true)
.Select(x => x.ApplicationModule);
but problem with this is, it doesn't issue a single query to database. It splits the query to get the webpages_Roles at SingleOrDefault then another query to get the Roles_X_ApplicationModules based on the RoleId and at the end as many queries as Roles_X_ApplicationModules matching the condition to get the ApplicationModule.
How do I write the LINQ query so that a single sql query is issued to database?
You can use Include() to do this.
Example:
card = Cards.Include(l => l.DocumentLinks)
.Include(l => l.Charges.Select(ch => ch.DocumentLinks)
.SingleOrDefault(c=>c.Id==id);
This is for three linked Entities:
public class Card
{
public Guid Id{get;set;}
public virtual ICollection<DocumentLink> DocumentLinks{get;set;}
public virtual ICollection<Charge> Charges{get;set;}
}
public class Charge
{
...
public virtual ICollection<DocumentLink> DocumentLinks{get;set;}
}
public class DocumentLink
{
...
}
try this:
var appModules = from u in user
from w in u.webpages_Roles
from am in w.Roles_X_ApplicationModules
where am.ViewPermission == true
select am;
if you want eager loading then you just need to call ToList:
var appModules = (from u in user
from w in u.webpages_Roles
from am in w.Roles_X_ApplicationModules
where am.ViewPermission == true
select am).ToList();

Delete entity but keep one of the related one

I need to delete a record of an entity but keep all the records of another entity that is related with it:
Entity record to remove is:
public class Ask
{
// Primary properties
public int Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public DateTime DateCreated { get; set; }
}
The related records that I want to keep after deleting an Ask record is of type MessageAsk :
public class Message
{
// Primary properties
public int Id { get; set; }
public string NameFrom { get; set; }
public string EmailFrom { get; set; }
public string TelephoneFrom { get; set; }
public string Title { get; set; }
public string MessageText { get; set; }
public bool? Approved { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateRead { get; set; }
// Navigation properties
public Member MemberFrom { get; set; }
public Member MemberTo { get; set; }
public MessageType MessageType { get; set; }
public Message MessageParent { get; set; }
}
public class MessageAsk : Message
{
public Ask Ask { get; set; }
}
Resuming, I want to delete an Ask and keep all related MessageAsk's.
EDIT:
I use the service Delete:
private readonly IRepository<Ask> _askRepository;
private readonly IRepository<MessageAsk> _messageAskRepository;
public bool Delete(int askId)
{
try
{
Ask askToDelete = _askRepository.GetById(askId);
IList<MessageAsk> relatedMessageAsks = _messageAskRepository.Query.Where(m => m.Ask.Id == askId).ToList();
_askRepository.Delete(askToDelete);
_askRepository.Save();
}
catch
{
return false;
}
return true;
}
And I use a repository to Delete the Entity:
public class Repository<T> : IRepository<T> where T : class
{
protected DbContext _dataContext;
protected DbSet<T> _dbSet;
public Repository(DbContext context)
{
_dataContext = context;
_dbSet = _dataContext.Set<T>();
}
public T NewEntityInstance()
{
return _dbSet.Create();
}
public void Delete(T entity)
{
if (_dataContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
public virtual void Delete(object id)
{
T entity = _dbSet.Find(id);
Delete(entity);
}
public T GetById(int id)
{
return _dbSet.Find(id);
}
public virtual IQueryable<T> Query
{
get
{
return _dbSet.AsNoTracking(); <------ SOURCE OF THE PROBLEM - I HAD TO REMOVE THE ASNOTRACKING OPTION TO SOLVE THE PROBLEM
}
}
}
Error I get now:
"The DELETE statement conflicted with the REFERENCE constraint "FK_Messages_Asks". The conflict occurred in database "Heelp", table "dbo.Messages", column 'Ask_Id'.
Thanks
If your relationship is optional (that is, the foreign key from MessageAsk table to Ask table allows NULL values), you can do it this way:
using (var context = new MyContext())
{
var askToDelete = context.Asks.Single(a => a.Id == askToDeleteId);
var relatedMessageAsks = context.MessageAsks
.Where(m => m.Ask.Id == askToDeleteId)
.ToList();
// or just: context.MessageAsks.Where(m => m.Ask.Id == askToDeleteId).Load();
context.Asks.Remove(askToDelete);
// or DeleteObject if you use ObjectContext
context.SaveChanges();
}
(or context.Messages.OfType<MessageAsk>()... instead of context.MessageAsks... if you don't have a set for the derived type in your context)
You don't need to set the MessageAsk.Ask property to null explicitly here. EF will do that automatically when the askToDelete is removed and update the MessageAsk with FK = NULL in the database.
It does not work if the relationship is required (no NULLs for the FK are allowed) as you would violate a referential foreign key constraint in the database when the principal (askToDelete) would be deleted. In that case you need to assign the relatedMessageAsks to another Ask before you delete the askToDelete.

Categories