Entity Framework One-To-Many - c#

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

Related

Problem using related entities in "database-first"

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.

Automapper Sub Property Mapping

I have a situation where I need to map a sub-collection of items within an object to a collection of items in another object. I am essentially trying to flatten the object for use by a consuming system.
Given the following entity classes:
public class PersonEntity
{
public int Id { get; set; }
public virtual ICollection<OutcomeEntity> Outcomes { get; set; }
}
public class OutcomeEntity
{
public int Id { get; set; }
public bool Outcome { get; set; }
public virtual ICollection<GradeEntity> Grades { get; set; }
public PersonEntity Person { get; set; }
}
public class GradeEntity
{
public int Id { get; set; }
public string Grade { get; set; }
public string MarkersComment { get; set; }
public OutcomeEntity Outcome { get; set; }
}
I need to map the OutcomeEntity and GradeEntity to the following flattened structure where there can be many outcomes, containing many different grades:
public class PersonDTO
{
public int PersonId { get; set; }
public virtual ICollection<GradeDTO> Grades { get; set; }
}
public class GradeDTO
{
public int OutcomeId { get; set; }
public int GradeId { get; set; }
public string Grade { get; set; }
public string MarkersComment { get; set; }
}
Basically, for every Outcome in the collection, I want to iterate over the grades within it and create a new object (GradeDTO).
I have attempted to create a basic map, but I simply cannot get my head around the sub-properties.
To create one collection from many you can use SelectMany extension method. With this method and the following configuration AutoMapper will create PersonDto from PersonEntity.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<GradeEntity, GradeDTO>()
.ForMember(dto => dto.GradeId, x => x.MapFrom(g => g.Id))
.ForMember(dto => dto.OutcomeId, x => x.MapFrom(g => g.Outcome.Id));
cfg.CreateMap<PersonEntity, PersonDTO>()
.ForMember(dto => dto.PersonId, x => x.MapFrom(p => p.Id))
.ForMember(dto => dto.Grades, x => x.MapFrom(p => p.Outcomes.SelectMany(o => o.Grades)));
});

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

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

Entity Framework 4.0 CTP5 One to One Mapping

Hi i am using CTP5 to map between two entities like that:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool IsManager { get; set; }
public decimal Credit { get; set; }
public int CreditAlertCount { get; set; }
public decimal TelPrice { get; set; }
public decimal CellPrice { get; set; }
public DateTime InsertDate { get; set; }
public IList<string> PhoneList { get; set; }
public int UserTypeId { get; set; }
public virtual UserType UserType { get; set; }
}
public class UserType
{
public int Id { get; set; }
public int UserLevel { get; set; }
public string TypeDescription { get; set; }
}
//here is configurations
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
HasKey(c => c.Id);
Property(c => c.Id).HasDatabaseGenerationOption(DatabaseGenerationOption.Identity).HasColumnName("ID");
Property(c => c.InsertDate).HasDatabaseGenerationOption(DatabaseGenerationOption.Computed).HasColumnName("INSERT_DATE");
Property(c => c.IsManager).HasDatabaseGenerationOption(DatabaseGenerationOption.Computed).HasColumnName("IS_MANAGER");
Property(c => c.UserName).HasMaxLength(25).IsRequired().HasColumnName("USER_NAME");
Property(c => c.Password).HasMaxLength(25).IsRequired().HasColumnName("USER_PASSWORD");
Property(c => c.CellPrice).IsRequired().HasColumnName("CELL_PRICE");
Property(c => c.TelPrice).IsRequired().HasColumnName("TEL_PRICE");
Property(c => c.CreditAlertCount).IsRequired().HasColumnName("CREDIT_ALERT_COUNT");
Property(c => c.Credit).IsRequired().HasColumnName("CREDIT");
Property(c => c.UserTypeId).IsOptional().HasColumnName("USER_TYPE_ID");
/*relationship*/
HasRequired(p => p.UserType).WithMany().IsIndependent().Map(m => m.MapKey(p => p.Id, "USER_TYPE_ID"));
ToTable("CRMC_USERS", "GMATEST");
}
}
public class UserTypeConfig : EntityTypeConfiguration<UserType>
{
public UserTypeConfig()
{
/*Identity*/
HasKey(c => c.Id);
Property(c => c.Id).HasDatabaseGenerationOption(DatabaseGenerationOption.Identity).HasColumnName("ID");
/*simple scalars*/
Property(s => s.TypeDescription).IsRequired().HasColumnName("DESCRITPION");
Property(s => s.UserLevel).IsRequired().HasColumnName("USER_LEVEL");
ToTable("CRMC_USER_TYPES", "GMATEST");
}
}
What do i do wrong my User.UserType = null?
How to hell do i map this to work!?
I am dying here for 3 days to work it off.
I'm using DevArt Connection 6.058... some thing
Oracle 10g, C# EntityFramework 4.0
You've setup a required association between User and UserType, therefore you cannot have a User object without a UserType (i.e. User.UserType == null). To be able to do that you need to make 2 changes to your object model and fluent API:
1.Change the type of UserTypeId property to int?:
public int? UserTypeId { get; set; }
2.Remove the code from your fluent API that reads:
HasRequired(p => p.UserType).WithMany().IsIndependent().Map(m => m.MapKey...
You don't need any of those stuff. Everything will be configured by Code First based on convention for you.

Categories

Resources