Problem using related entities in "database-first" - c#

I am learning MVC and for do this I am developing a "smart forum". I have do a database but I have some problem with entities. I have do this command
"Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=SmartForum;Trusted_Connection=True; Microsoft.EntityFrameworkCore.SqlServer -OutputDir ModelsFromDb" ,
A code snippet:
modelBuilder.Entity<ArgomentiPerArea>(entity =>
{
entity.HasKey(e => e.ArgomentoId);
entity.Property(e => e.ArgomentoId).HasColumnName("argomentoId");
entity.Property(e => e.Archiviato).HasColumnName("archiviato");
entity.Property(e => e.AreaId).HasColumnName("areaId");
entity.Property(e => e.ModeratoreId).HasColumnName("moderatoreId");
entity.Property(e => e.NomeArgomento).HasColumnName("nome_argomento");
entity.Property(e => e.NumeroRigaPerArea).HasColumnName("numero_riga_per_area");
entity.Property(e => e.TestoPerArgomento).HasColumnName("testo_per_argomento");
entity.HasOne(d => d.Area)
.WithMany(p => p.ArgomentiPerArea)
.HasForeignKey(d => d.AreaId)
.HasConstraintName("FK_ArgomentiPerArea_Aree");
entity.HasOne(d => d.Moderatore)
.WithMany(p => p.ArgomentiPerArea)
.HasForeignKey(d => d.ModeratoreId)
.HasConstraintName("FK_ArgomentiPerArea_Moderatori");
});
a second snippet :
public partial class ArgomentiPerArea
{
public ArgomentiPerArea()
{
Thread = new HashSet<Thread>();
}
[Key]
public int ArgomentoId { get; set; }
public string NomeArgomento { get; set; }
public int? AreaId { get; set; }
public bool? Archiviato { get; set; }
public int? NumeroRigaPerArea { get; set; }
public string TestoPerArgomento { get; set; }
public int? ModeratoreId { get; set; }
public virtual Aree Area { get; set; }
public virtual Moderatori Moderatore { get; set; }
public virtual ICollection<Thread> Thread { get; set; }
}
public partial class Aree
{
public Aree()
{
ArgomentiPerArea = new HashSet<ArgomentiPerArea>();
}
[Key]
public int AreaId { get; set; }
public string NomeArea { get; set; }
public int? NumeroRiga { get; set; }
public int? NumeroColonna { get; set; }
public virtual ICollection<ArgomentiPerArea> ArgomentiPerArea { get; set; }
}
public partial class Moderatori
{
public Moderatori()
{
ArgomentiPerArea = new HashSet<ArgomentiPerArea>();
SegnalazioniPerModeratori = new HashSet<SegnalazioniPerModeratori>();
}
[Key]
public int ModeratoreId { get; set; }
public string UsernameModeratore { get; set; }
public string PasswordHash { get; set; }
public string NomeCognome { get; set; }
public bool? Archiviato { get; set; }
public virtual ICollection<ArgomentiPerArea> ArgomentiPerArea { get; set; }
public virtual ICollection<SegnalazioniPerModeratori> SegnalazioniPerModeratori { get; set; }
}
when this code run
public class ArgomentiPerAreasController : Controller
{
private ModelsFromDb.SmartForumContext db = new ModelsFromDb.SmartForumContext();
// GET: ArgomentiPerAreas
public ActionResult Index()
{
var argomentiPerAreas = db.ArgomentiPerArea.Include(a => a.Area).Include(a => a.Moderatore);
string msg = "m";
return View(argomentiPerAreas.ToList());
}
.............
.............}
I check in view and "moderatore" and "area" have null value.
I don't understand but I know database first and MVC superficially. I hope in some suggestions.

This is likely due to circular references between your Argomenti* & the Moderator/Area. An Area holds a collection back to the Argomenti* so when MVC goes to serialize the root entity (Argomenti) it comes across the Area, then iterating through the area, a collection of Argomenti*, and co the cycle goes. It bails out and doesn't attempt to serialize the cyclical dependencies.
Generally the best thing to do with EF and views is not to attempt to send entities to the view. Instead, create a POCO (Plain old C# object) View Model to send to the view. This view model contains only the fields needed by the view, and your EF query uses .Select() to populate that view model. This avoids the whole cyclic reference issue, and negates the need for deliberate eager loading (.Include()) or the performance risk of lazy loading.
For example: If I want a list of Argumenti, and I want to display each Area and Moderator as part of that:
[Serializable]
public class ArgumentiViewModel
{
public string NomeArgomento { get; set; }
public bool? Archiviato { get; set; }
public int? NumeroRigaPerArea { get; set; }
public string TestoPerArgomento { get; set; }
public string NomeArea { get; set; } // From Area
public string NomeCognome { get; set; } // From Moderator
}
Then when I want to return this to the view:
var argomentiViewModels = db.ArgomentiPerArea
.Select(x => new ArgomentiViewModel
{
NomeArgomento = x.NomeArgomento,
Archiviato - x.Archiviato,
NumeroRigaPerArea = x.NumeroRigaPerArea,
TestoPerArgomento = x.TestoPerArgomento,
NomeArea = x.Area.NomeArea, // From Area
NomeCognome = x.Moderatori.NomeCognome // From Moderator
}).ToList();
string msg = "m";
return View(argomentiViewModels);
I sumarized a couple good reasons why code should not return Entities to the view here.

Related

Map variable from DTO class using LINQ

I have two classes called Participant and Screen.
public class Participant
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Screen> Screens { get; set; }
}
public class Screen
{
public int Id { get; set; }
public DateTime? SignedDateTime { get; set; }
}
And I use a DTO as below:
public class ParticipantForDashboardDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? ScreenDateTime { get; set; }
public ICollection<Screen> Screens { get; set; }
}
In here, since I don't have ScreenDateTime field in my Participant class, I decided to get this field from Screen class using AutoMapper. Here is my MappingProfile:
CreateMap<Participant, ParticipantForDashboardDto>()
.ForMember(dest => dest.ScreenDateTime, opt => {
opt.MapFrom(src => src.Screens.Select(x => x.SignedDateTime));
});
This returns 0001-01-01T00:00:00. I suspect that the SELECT query is not the one I should use. How can I map SignedDateTime from Screen class to ScreenDate in the dto?
Your currently using a queryable, which will return multiple result, you probably on need one, so you need to create a way to resolve the proper one, e.g sort and first or default:
CreateMap<Participant, ParticipantForDashboardDto>()
.ForMember(dest => dest.ScreenDateTime, opt => {
opt.MapFrom(src => src.Screens.Select(x => x.SignedDateTime)
.OrderByDescending(x => x)
.FirstOrDefault());
});

EF include list is always null

By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);

AutoMapper with different children

I have an entity as Plan with multiple sub-plans (children), each of which could be null.
For the PlanDto, I am trying to load up a list of all children rather than having a separate property for each child like the entity.
I have already achieved it manually through a foreach loop but now I am trying to do it via AutoMapper, which is failing for some reason.
Entities:
public class Plan
{
public virtual int Id { get; set; }
public DateTime Date { get; set; }
public virtual PlanDetail PlanChild1 { get; set; }
public virtual ObservationCare PlanChild2 { get; set; }
}
public class PlanDetail
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
public class ObservationCare
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
DTOs:
public class PlanDto: EntityDto
{
public DateTime Date { get; set; }
public IEnumerable<ChildPlan> ChildPlan { get; set; }
}
public class ChildPlan : EntityDto
{
public ChildPlanType Type { get; set; }
}
public enum ChildPlanType
{
PlanDetail,
ObservationCare
}
AutoMapper config:
configuration.CreateMap<Plan, PlanDto>();
configuration.CreateMap<PlanDetail, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.PlanDetail));
configuration.CreateMap<ObservationCare, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.ObservationCare));
Mapping attempt:
var output = new List<PlanDto>();
var plans = await _planRepository.GetAll().ToList();
foreach (var plan in plans)
{
output.Add(ObjectMapper.Map<PlanDto>(plan));
}
I do not know why ChildPlan DTOs in the output list are always null!
You have to specify the mapping for PlanDto.ChildPlan:
configuration.CreateMap<Plan, PlanDto>()
.ForMember(dto => dto.ChildPlan,
options => options.MapFrom(
p => new object[] { p.PlanChild1, p.PlanChild2 }.Where(c => c != null)));
If you are using Entity Framework Core, you have to use eager-loading:
var plans = await _planRepository.GetAll()
.Include(p => p.PlanChild1)
.Include(p => p.PlanChild2)
.ToList();
There's also a simpler and more efficient way to map a list:
var output = ObjectMapper.Map<List<PlanDto>>(plans);

Define column name for self-referencing List property in EF

I am defining a class based on a cataloguing Authority entry, which has a number of self referencing children, as follows:
public class Authority
{
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
public Authority Use { get; set; }
public List<Authority> UsedFor { get; set; }
public List<Authority> Equivalent { get; set; }
public List<Authority> Broader { get; set; }
public List<Authority> Narrower { get; set; }
}
When the columns are created in the Authority table in the underlying SQL database, the column names for each of the List properties are Authority_ID, Authority_ID1, Authority_ID2 and Authority_ID3.
I would rather the column names to be 'UsedFor', 'Equivalent', 'Broader' and 'Narrower'. I have tried using the [Column("name")] attribute but it does not work. How can I do this in Code First?
Try this. I am not sure if the definitions of the foreign keys must be in the POCO class (you can try to omit them).
public class Authority
{
[Key()]
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
public Authority Use { get; set; }
[Required]
public long Authority_ID { get; set; }
[Required]
public long Authority_ID1 { get; set; }
[Required]
public long Authority_ID2 { get; set; }
[Required]
public long Authority_ID3 { get; set; }
[ForeignKey("Authority_ID")]
public ICollection<Authority> UsedFor { get; set; }
[ForeignKey("Authority_ID1")]
public ICollection<Authority> Equivalent { get; set; }
[ForeignKey("Authority_ID2")]
public ICollection<Authority> Broader { get; set; }
[ForeignKey("Authority_ID3")]
public ICollection<Authority> Narrower { get; set; }
}
You can use [InverseProperty("name")] for your list. After that, your column names will be "UsedFor_ID", "Equilavent_ID", etc in the database (not quite corresponding to your question, sorry!).
public class Authority
{
[InverseProperty("UsedFor")]
public List<Authority> UsedFor { get; set; }
[InverseProperty("Equivalent")]
public List<Authority> Equivalent { get; set; }
}
See more at:
https://msdn.microsoft.com/en-us/data/gg193958
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
Thanks for the suggestions. What worked was to use [ForeignKey] for the link and [Column] to rename the column, i.e.
[Column("Use")]
public long? UseID { get; set; }
[ForeignKey("UseID")]
public List<Authority> Use { get; set; }
However I have also made a critical mistake in the definition because even though I defined the column as a List, the code above ends up with a 1-to-0/1 key. What I really needed to do was to add in a child table to accept the many values.
My final code looks like this, and the underlying column names are readable instead of Authority_ID, Authority_ID1, Authority_ID2 and Authority_ID3:
public class Authority
{
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
//Establish 1-to-0/1 self-referencing key
[Column("Use")]
public long? UseID { get; set; }
[ForeignKey("UseID")]
public List<Authority> Use { get; set; }
//Establis 1-many foreign keys
[ForeignKey("UsedFor")]
public List<AuthorityList> UsedFor { get; set; }
[ForeignKey("Equivalent")]
public List<AuthorityList> Equivalent { get; set; }
[ForeignKey("Broader")]
public List<AuthorityList> Broader { get; set; }
[ForeignKey("Narrower")]
public List<AuthorityList> Narrower { get; set; }
}
public class AuthorityList
{
public long ID { get; set; }
public long AuthorityID { get; set; }
public long? UsedFor { get; set; }
public long? Equivalent { get; set; }
public long? Broader { get; set; }
public long? Narrower { get; set; }
}
In order to prevent cascading deletes getting in the way I have also added the following into my database context (affects entire DB not just these tables):
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Updated answer:
Using the above gave me the underlying table structure I wanted but not the functionality I required since EF decided that under that configuration it was going to map a 1-1 relationship instead of 1-M. The answer lay in understanding how EntityFramework manages self-referencing Many-to-Many relationships. Even this can be configured multiple ways depending on whether you only want two 1-M relationships or more. I want six.
In the end, this configuration gave me the functionality I wanted, to the expense of having a less than ideal database structure.
public class Tag
{
public int ID { get; set; }
public string Term { get; set; }
public virtual ICollection<Tag> Broader { get; set; }
public virtual ICollection<Tag> Narrower { get; set; }
public virtual ICollection<Tag> Equivalent { get; set; }
public virtual ICollection<Tag> Related { get; set; }
public virtual ICollection<Tag> Use { get; set; }
public virtual ICollection<Tag> Usefor { get; set; }
public Tag()
{
Broader = new HashSet<Tag>();
Narrower = new HashSet<Tag>();
Equivalent = new HashSet<Tag>();
Related = new HashSet<Tag>();
Use = new HashSet<Tag>();
Usefor = new HashSet<Tag>();
}
}
I also needed to add the following entries into the 'OnModelCreating' procedure of the database context:
modelBuilder.Entity<Tag>()
.HasMany(x => x.Broader)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("BroaderID").ToTable("TagBroader"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Equivalent)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("EquivalentID").ToTable("TagEquivalent"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Narrower)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("NarrowerID").ToTable("TagNarrower"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Related)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("RelatedID").ToTable("TagRelated"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Use)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("UsedID").ToTable("TagUse"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Usedfor)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("UsedforID").ToTable("TagUsedfor"));
To test, I used the following:
//Broader/Narrower example
var music = new Tag{ Term = "Music"};
var jazz = new Tag{ Term = "Jazz Music" };
var classical = new Tag{ Term = "Classical Music" };
music.Narrower.Add(jazz);
music.Narrower.Add(classical);
jazz.Broader.Add(music);
classical.Broader.Add(music);
//Equivalent example
var zucchini = new Tag{ Term = "Zucchini" };
var courgette = new Tag{ Term = "Courgette" };
zucchini.Equivalent.Add(courgette);
courgette.Equivalent.Add(zucchini);
context.Tags.Add(music);
context.Tags.Add(jazz);
context.Tags.Add(classical);
context.Tags.Add(zucchini);
context.Tags.Add(courgette);
context.SaveChanges();

Entity Framework One-To-Many

First of all I have these two models to store a post in two tables one for shared data and the other contains cultured data for English and Arabic
public class Post
{
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public virtual ICollection<PostContent> Contents { get; set; }
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }
public int PostId { get; set; }
[InverseProperty("PostId")]
public virtual Post Post{ set; get; }
}
Mapping
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(p => p.Post).WithMany(p => p.Contents).HasForeignKey(p=>p.PostId);
ToTable("PostContents");
}
}
I have two questions
1- Is these models are connected properly. Is there something else I need to do ?
2- I need to select all Posts with their contents where the culture of the content 'en' for example. I used this:
var res = context.Posts.Include(p => p.Contents.Single(c => c.Culture.Equals("en")));
and have this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.Parameter name: path
If you know you are not going to support more than two cultures then I would just add to your Post class.
public class Post
{
public Post()
{
Contents = new List<PostContent>();
}
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public int? EnglishContentId { get;set;}
public int? ArabicContentId { get;set;}
PostContent EnglishContent {get;set;}
PostContent ArabicContent {get;set;}
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }/*This property is not required*/
}
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
HasOptional(p => p.EnglishContent).WithMany().HasForeignKey(p=>p.EnglishContentId);
HasOptional(p => p.ArabicContent).WithMany().HasForeignKey(p=>p.ArabicContentId);
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("PostContents");
}
}
The Above design will simplify your design and queries, will improve the performance alot.
But if you might have to support more cultures then you got the design and mapping right.
As far as EF 5, include does not allow filters, but I am not sure about EF 6.0
atleast you can get all posts that have english contents as follows
Add using System.Data.Entity;
var res = context.Posts.Include(p => p.Contents).Where(c => c.Contents.Any(cp=>cp.Culture.Equals("en")));

Categories

Resources