I'm building a wiki, which has articles that consists of subarticles. An article can consist of multiple subarticles, and a subarticle can be attached to multiple articles. In the jointable there is a sortorder that defines the display of subarticles for a particular article.
The parent-article only consists of a title (and metadata), no text, all text is done through subarticles.
This sortorder in the jointable though, is where I'm stuck atm, I can't access it from my query. Hopefully someone can point me in the right direction.
Sidenote: I'm quite new in the whole MVC/EF world, even c#/vb and .NET is something I've only been working on since a few months and in my spare time.
I have these classes:
Article:
public class Article : BaseEntity
{
private ICollection<Category> _categories;
private ICollection<ArticleSubarticle> _subarticles;
public string Title { get; set; }
public int AuthorId { get; set; }
public DateTime CreationDate { get; set; }
public DateTime ?PublishDate { get; set; }
public DateTime ?ChangeDate { get; set; }
public bool Published { get; set; }
public virtual ICollection<Category> Categories
{
get { return _categories ?? (_categories = new List<Category>()); }
protected set { _categories = value; }
}
public virtual ICollection<ArticleSubarticle> Subarticles
{
get { return _subarticles ?? (_subarticles = new List<ArticleSubarticle>()); }
protected set { _subarticles = value; }
}
}
Subarticle
public class Subarticle : Article
{
private ICollection<Attachment> _attachments;
public string ArticleText { get; set; }
public int OriginalArticle { get; set; }
public bool Active { get; set; }
public virtual ICollection<Attachment> Attachments
{
get { return _attachments ?? (_attachments = new List<Attachment>()); }
protected set { _attachments = value; }
}
}
Jointable:
public class ArticleSubarticle : BaseEntity
{
public int ParentId { get; set; }
public int ChildId { get; set; }
public int SortOrder { get; set; }
public virtual Article Parent { get; set; }
public virtual Subarticle Child { get; set; }
}
They are mapped as follows:
Article
public ArticleMap () {
ToTable("Wiki_Article");
HasKey(a => a.Id);
Property(a => a.Title).HasColumnType("VARCHAR").HasMaxLength(250);
Property(a => a.AuthorId);
Property(a => a.PublishDate).IsOptional();
Property(a => a.ChangeDate).IsOptional();
HasMany(a => a.Categories)
.WithMany()
.Map(a => a.ToTable("Wiki_Article_Category_Mapping"));
}
Subarticle
public SubarticleMap()
{
ToTable("Wiki_Subarticle");
HasKey(sa => sa.Id);
Property(a => a.ArticleText)
.IsOptional()
.HasColumnType("TEXT");
Property(a => a.OriginalArticle)
.IsOptional();
HasMany(a => a.Attachments)
.WithMany()
.Map(a => a.ToTable("Wiki_Subarticle_Attachment_Mapping"));
}
Jointable
public ArticleSubarticleMap()
{
ToTable("Wiki_Article_Subarticle_Mapping");
HasKey(asa => new { asa.ParentId, asa.ChildId });
HasRequired(asa => asa.Parent)
.WithMany(asa => asa.Subarticles)
.HasForeignKey(asa => asa.ParentId);
}
This gets me the database as expected.
Now I want an article with its subarticles, that are ordered by the sortorder.
This query gets me the article with its subarticles, but I can't seem to figure out how to reach this sortorder in the Wiki_Article_Subarticle_Mapping table.
public IList<Article> getArticleByIdWithSortedSubarticles(int ArticleId)
{
var query = _articleRepository.Table;
query = query.Where(a => ArticleId == a.Id)
.Select(a => a);
var subarticles = query.ToList();
return subarticles;
}
Any ideas?
Thanks in advance!
Your query is not loading subarticles currently, so I guess they are lazily loaded. Try loading them explicitly like this:
public IList<Article> getArticleByIdWithSortedSubarticles(int ArticleId)
{
var query = _articleRepository.Table;
query = query.Where(a => ArticleId == a.Id)
.Select(a => new { article = a, subs = a.SubArticles.OrderBy(s => s.SortOrder) });
return query.AsEnumerable().Select(m => m.article).ToList();
}
Related
I'm new to C# / .NET Core and I'm trying to create a Web API. I have 2 models; Community & Rank
Community:
public class Community
{
public long Id { get; set; }
public long FrontId { get; set; }
public string Name { get; set; }
[ForeignKey("CommunityId")]
public virtual ICollection<Rank> Ranks { get; set; }
}
Rank:
public class Rank
{
public long Id { get; set; }
public long CommunityId { get; set; } [Required]
public string Name { get; set; } [Required]
public string Prefix { get; set; }
public virtual Community Community { get; set; }
}
I have my context set up like so:
public class CommunityContext : DbContext
{
public DbSet<Community> Communities { get; set; }
public DbSet<Rank> Ranks { get; set; }
public CommunityContext(DbContextOptions<CommunityContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//Communities
builder.Entity<Community>(entity =>
{
entity.HasKey(c => c.Id);
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
entity.HasKey(r => r.Id);
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
}
}
I've done my database migrations etc, and when creating new entries, all works as expected currently, however trying to GET either communities or ranks returns the error:
MySql.Data.MySqlClient.MySqlException (0x80004005): Unknown column 'r.CommunityId1' in 'field list'
Any suggestions would be appreciated!
Thanks
Your Configurations should be like this:
...
//Communities
builder.Entity<Community>(entity =>
{
builder.ToTable("Community");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("CommunityId").ValueGeneratedOnAdd();
entity.Property(c => c.Name).IsRequired();
entity.Property(c => c.FrontId).IsRequired();
// entity.Property(c => c.Ranks).IsRequired(false);
entity.HasMany(c => c.Ranks).WithOne().HasForeignKey(c => c.CommunityId).IsRequired();
});
//Ranks
builder.Entity<Rank>(entity =>
{
builder.ToTable("Rank");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).HasColumnName("RankId").ValueGeneratedOnAdd();
entity.Property(r => r.Name).IsRequired();
entity.Property(r => r.Prefix).IsRequired();
entity.Property(r => r.CommunityId).HasColumnName("CommunityId").IsRequired();
// entity.HasOne(r => r.Community).WithMany(s => s.Ranks);
});
...
How to mapping IdContributors (collection of strings) to a collection (Contributors), inside a collection TAction, with a property (ContributorId) of string, using LINQ and AutoMapper ?
public ViewModelToDomainMappingProfile()
{
CreateMap<ActionViewModel, TAction>();
//.ForMember(d => d.Contributors, opt => opt.MapFrom(a => ids = a.IdContributors.Select(x => { })));
}
Models
Model TAction
public class TAction
{
public Guid Id {get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<TActionContributor> Contributors { get; set; }
}
public class TActionContributor
{
public Guid TActionId { get; set; }
[ForeignKey("TActionId")]
public TAction Action { get; set; }
public string ContributorId { get; set; }
[ForeignKey("ContributorId")]
public ApplicationUser Contributor { get; set; }
}
ActionViewModel
public class ActionViewModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<string> IdContributors { get; set; }
}
I can see two choices here, you'll choose the solution which better suits your needs:
1) Select() in from MapFrom()
CreateMap<ActionViewModel, TAction>()
.ForMember(
destination => destination.Contributors,
options => options.MapFrom((source, destination) =>
{
var contributors = source
.IdContributors
.Select(id => new TActionContributor
{
Action = destination,
Contributor = new ApplicationUser
{
Id = id,
},
ContributorId = id,
TActionId = source.Id,
})
.ToList();
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
return contributors;
}));
2) Custom ValueResolver
This is the same as the first solution, maybe more readable if you want to keep your mapping profiles clean and move the custom logic away when possible.
public class TActionContributorValueResolver : IValueResolver<ActionViewModel, TAction, ICollection<TActionContributor>>
{
public ICollection<TActionContributor> Resolve(
ActionViewModel source,
TAction destination,
ICollection<TActionContributor> destMember,
ResolutionContext context)
{
var contributors = source
.IdContributors
.Select(id => new TActionContributor
{
Action = destination,
Contributor = new ApplicationUser
{
Id = id,
},
ContributorId = id,
TActionId = source.Id,
})
.ToList();
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
return contributors;
}
}
Configuration:
CreateMap<ActionViewModel, TAction>()
.ForMember(
destination => destination.Contributors,
options => options.MapFrom<TActionContributorValueResolver>());
Final note:
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
Line above allows you to traverse endlessly from TAction through TActionContributor to ApplicationUser and the other way. If you don't need that feature, feel from to remove it and return the contributors list right away.
I have a use-case with a deeply nested class hierarchy, for example like this:
public class Parent
{
public int Id { get; set; }
public List<ChildOne> Children { get; set; }
}
public class ChildOne
{
public int Id { get; set; }
public int ParentId { get; set; }
public List<ChildTwo> ChildrenTwo { get; set; }
}
public class ChildTwo
{
public int Id { get; set; }
public int Priority { get; set; }
public int ChildOneId { get; set; }
public List<ChildThree> ChildrenThree { get; set; }
}
public class ChildThree
{
public int Id { get; set; }
public int ChildTwoId { get; set; }
}
If I want to load all parent-objects and their related children levels, I'd do this:
var objects = context.Parent
.Include(parent => parent.Children)
.ThenInclude(childOne => childOne.ChildrenTwo)
.ThenInclude(childTwo => childTwo.ChildrenThree)
.ToList();
But what if I want my ChildrenTwo entities in the eager-loaded navigational property of ChildOne to be ordered by their Priority? I've done some research, and from the links below (and some others), it is apparently not directly possible in EF Core (yet):
https://github.com/aspnet/EntityFrameworkCore/issues/9445
https://github.com/aspnet/EntityFrameworkCore/issues/2919
https://github.com/aspnet/EntityFrameworkCore/issues/9067
So, how can you achieve the ordering of the ChildrenTwo above (by Priority) in a good/clean way that is fast? That probably means most of the work should happen on the DB server and not on the .NET client side. What's the best approach here?
Though it is very late to answer, but it may help the future readers:
I will explain the code:
var authorArticles = await _context.AuthorArticles
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pq => pq.Qualifications)
.ThenInclude(q => q.QualificationSubject)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.ThenInclude(prof => prof.Profession)
.Where(aa => aa.ArticleId == articleId)
.Select(s => new AuthorArticle
{
Author = new Author
{
Affiliation = s.Author.Affiliation,
AvailableAsReviewer = s.Author.AvailableAsReviewer,
Person = new Person
{
Email = s.Author.Person.Email,
FirstName = s.Author.Person.FirstName,
LastName = s.Author.Person.LastName,
MiddleName = s.Author.Person.MiddleName,
Title = s.Author.Person.Title,
FullName = s.Author.Person.FullName,
UserId = s.Author.Person.UserId,
Professions = new Collection<PersonProfession>
{
new PersonProfession
{
// using sorting here!!
Organization = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Organization,
Profession = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Profession
}
},
Qualifications = new Collection<PersonQualification>
{
new PersonQualification
{
QualificationSubject = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationSubject,
QualificationLevelId = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationLevelId
}
}
}
},
IsCorresponding = s.IsCorresponding,
AuthorPosition = s.AuthorPosition
}).ToListAsync();
return authorArticles;
If you simply eager loaded the entities, then at the time of projection; which means when you are selecting the items from the query, you can recreate the object that has already been provided in slightly different way. In my case, I wanted only one profession of the person out of many and same goes for the qualification of the person.
Took help of select from Another SO great answer!
I have a DB structure which is not ideal, but I have coded it into EF like this:
[Table("Item")]
public class Item
{
[Key] public int Id { get; set; }
public int CategoryId { get; set; }
public int ItemTypeId { get; set; } // An ItemTypeId of 1 means this row refers to an Article
public int ItemId { get; set; } // this refers to the Article primary key
}
[Table("Article")]
public class Article
{
[Key] public int Id { get; set; }
...
public virtual ICollection<SubArticle> SubArticles { get; set; }
}
[Table("SubArticle")]
public class SubArticle
{
...
public int ArticleId { get; set; }
}
modelBuilder.Entity<Article>().Collection(_ => _.SubArticles).InverseReference(_ => _.Article).ForeignKey(_ => _.ArticleId);
What I want to do is get all articles (with the corresponding sub-articles) that belong to a specific category. I have this query which is working:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>().Include(a => a.SubArticles),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.ToListAsync();
result.SubArticles.First(); // works
My question is why does this query not work:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>(),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.Include(a => a.SubArticles)
.ToListAsync();
result.SubArticles.First(); // error: result.SubArticles is null
I am using Telerik MVC Grid, have two tables:
public partial class Sleep
{
public int ID { get; set; }
public string PHN { get; set; }
public System.DateTime Day { get; set; }
public Nullable<int> SleepLevelId { get; set; }
public Nullable<int> SDuration { get; set; }
public string Comment { get; set; }
public virtual SleepLevel SleepLevel { get; set; }
}
public partial class SleepLevel
{
public SleepLevel()
{
this.Sleep = new HashSet<Sleep>();
}
public int SleepLevelId { get; set; }
public string TheLevel { get; set; }
public virtual ICollection<Sleep> Sleep { get; set; }
}
}
I use the second model for dropdownlist. Here how I generate the Grid Model:
private IEnumerable<Sleep> GePB ()
{
....
var sleep = db.Sleep.Include(s => s.SleepLevel).Where(d => d.PHN == phn && d.Day <= toDate);
return sleep;
}
[GridAction]
public ActionResult _AjaxBinding ()
{
return View(new GridModel<Sleep> { Data = GePB() });
}
In the Grid I use:
#(Html.Telerik().Grid<Sleep>()
.Name("Grid")
.DataKeys(keys => keys.Add(p => p.ID))
.ToolBar(commands => commands.Insert().ButtonType(GridButtonType.ImageAndText).ImageHtmlAttributes(new
{
style = "margin-left:0;"
}))
.DataBinding(dataBinding => dataBinding.Ajax()
.Select("_SelectAjaxEditing", "Sleep")
.Insert("_InsertAjaxEditing", "Sleep")
.Update("_SaveAjaxEditing", "Sleep")
.Delete("_DeleteAjaxEditing", "Sleep"))
.Columns(columns =>
{
columns.Bound(p => p.Day).Width(70).Format("{0:d}");
columns.Bound(o => o.SleepLevel.TheLevel).Width(70);
columns.Bound(o => o.SDuration).Width(70);
columns.Command(commands =>
{
commands.Edit().ButtonType(GridButtonType.Image);
commands.Delete().ButtonType(GridButtonType.Image);
}).Width(50).Title("Edit");
})
.Editable(editing => editing.Mode(GridEditMode.InLine))
.Pageable()
.Scrollable()
.Sortable().Filterable()
)
I get the following error:
Unable to get property 'TheLevel' of undefined or null reference
Would appreciate your suggestions.
There is no property in the Sleep class that is a StressLevel type. So I'll address the error message as best as possible.
The error
Unable to get property 'TheLevel' of undefined or null reference
is because this line
columns.Bound(o => o.SleepLevel.TheLevel).Width(70);
is trying to access a property of the SleepLevel class called TheLevel that most probably does not exist in your Sleep class.