Navigation mapping issues in EF Core - c#

I'm trying to achieve the following layout:
User table (has link to user details)
User details table (holds links to various detail tables)
but am getting the following error:
System.InvalidOperationException: Cannot create a relationship between 'Address.ClientDetails' and 'ClientDetails.ResidentialAddress', because there already is a relationship between 'ClientDetails.PostalAddress' and 'Address.ClientDetails'. Navigation properties can only participate in a single relationship.
I understand this problem would occur if entity framework had no way to identify which address to link to each address - but i thought i take care of that by specifying 2 links in the model and then each key map in the mapping class. Any help would be great!
my Client model and mapping looks as follows:
public class Client : BaseEntity
{
public ClientDetails ApplicantDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Applicant.ToString());
}
}
public ClientDetails SpouseDetails
{
get
{
return this.ClientDetails.SingleOrDefault(e => e.ClientType == Enums.ClientType.Spouse.ToString());
}
}
public ICollection<ClientDetails> ClientDetails { get; set; }
public ICollection<BankDetails> BankDetails { get; set; }
public ICollection<Expenses> Expenses { get; set; }
public ICollection<Obligation> Obligations { get; set; }
public ICollection<Budget> Budgets { get; set; }
public ICollection<Document.Document> Documents { get; set; }
public virtual Workflow.Workflow Workflow { get; set; }
Mapping
public class ClientMapping: IEntityTypeConfiguration<Entities.Client.Client>
{
public void Configure(EntityTypeBuilder<Entities.Client.Client> builder)
{
builder.ToTable("Client");
builder.HasKey(e => e.Id);
builder.HasMany(e => e.ClientDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Documents).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasOne(e => e.Workflow).WithOne(e => e.Client).HasForeignKey<Entities.Workflow.Workflow>(e => e.ClientId);
builder.HasMany(e => e.Obligations).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Expenses).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.Budgets).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.HasMany(e => e.BankDetails).WithOne(e => e.Client).HasForeignKey(e => e.ClientId);
builder.Ignore(e => e.ApplicantDetails);
builder.Ignore(e => e.SpouseDetails);
}
}
Client Details and mapping
public class ClientDetails
{
public int ClientId { get; set; }
public int PersonalDetailsId { get; set; }
public int EmployerId { get; set; }
public int ResidentialAddressId { get; set; }
public int PostalAddressId { get; set; }
public int IncomeId { get; set; }
public string ClientType { get; set; }
public virtual Client Client { get; set; }
public virtual PersonalDetails PersonalDetails { get; set; }
public virtual Employer Employer { get; set; }
public virtual Address ResidentialAddress { get; set; }
public virtual Address PostalAddress { get; set; }
public virtual Income Income { get; set; }
}
mapping
public class ClientDetailsMapping : IEntityTypeConfiguration<Entities.Client.ClientDetails>
{
public void Configure(EntityTypeBuilder<ClientDetails> builder)
{
builder.ToTable("ClientDetails");
builder.HasKey(e => new { e.IncomeId, e.PersonalDetailsId, e.ClientId, e.PostalAddressId, e.ResidentialAddressId } );
builder.HasOne(e => e.Income).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.IncomeId);
builder.HasOne(e => e.PostalAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);
builder.HasOne(e => e.Employer).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.EmployerId);
builder.HasOne(e => e.PersonalDetails).WithOne(e => e.ClientDetails).HasForeignKey<ClientDetails>(e => e.PersonalDetailsId);
}
}

Can you try to delete the content of "WithOne"?
try this:
builder.HasOne(e => e.PostalAddress).WithOne().HasForeignKey<ClientDetails>(e => e.PostalAddressId);
builder.HasOne(e => e.ResidentialAddress).WithOne().HasForeignKey<ClientDetails>(e => e.ResidentialAddressId);

Found this post:
ef core - two one to one on one principal key
Ended up implementing solution 3, client details now has a collection of addresses (which have an address type linked), I then added an address getter on the client details to get the address I want at a later time. Everything seems to work correctly now.

Related

ASP .Net Core: Many-to-Many relationship and AutoMapper

I have 2 entities connected by a many to many relationship.
First class:
public class ArticleCategory
{
public int Id {get; set; }
public string MainCategoryName { get; set; }
public List<ArticleCategorySubcategory> ArticleCategorySubcategories { get; set; } = new List<ArticleCategorySubcategory>();
public bool IsActive { get; set; }
}
Second class:
public class ArticleSubcategory
{
public int Id { get; set; }
public string SubcategoryName { get; set; }
public List<ArticleCategorySubcategory> ArticleCategorySubcategories { get; set; } = new List<ArticleCategorySubcategory>();
}
And relationship (many to many):
public class ArticleCategorySubcategory : BaseHistoryEntity
{
public int Id { get; set; }
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory { get; set; }
public int ArticleSubcategoryId { get; set; }
public ArticleSubcategory ArticleSubcategory {get; set;}
}
And I have also 1 DTO:
public class ArticleCategoryResult
{
public string CategoryName { get; set; }
public List<string> Subcategories { get; set; }
public bool IsActive { get; set; }
}
I want to use AutoMapper to list the names of subcategories. I tried something like this but I get an empty list.
My Automapper code:
CreateMap<ArticleCategory, ArticleCategoryResult>()
.ForMember(dst => dst.CategoryName, opt => opt.MapFrom(src => src.MainCategoryName))
.ForMember(dst => dst.IsActive, opt => opt.MapFrom(src => src.IsActive))
.ForMember(dst => dst.Subcategories, src => src.MapFrom(mbr => mbr.ArticleCategorySubcategories.Select(x => x.ArticleSubcategory.SubcategoryName)));
Result on view as json:
{
"categoryName": "Example category 6",
"subcategories": [],
"isActive": true
}
This is what my configuration looks like for these tables:
public void Configure(EntityTypeBuilder<ArticleCategorySubcategory> builder)
{
builder
.HasKey(x => x.Id);
builder
.HasOne(x => x.ArticleCategory)
.WithMany(x => x.ArticleCategorySubcategories)
.HasForeignKey(x => x.ArticleCategoryId);
builder
.HasOne(x => x.ArticleSubcategory)
.WithMany(x => x.ArticleCategorySubcategories)
.HasForeignKey(x => x.ArticleSubcategoryId);
}
How can I list the names of subcategories using AutoMapper?
You need to add .Include(x=>x.ArticleSubcategory) before your .Select(.. as the related objects are not tracked and are treated as undefined.

Automapper map nested different object properties

I have the following Company and its nested object CompanyEmployee:
public class Company
{
public string Id { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public List<CompanyEmployee> Employees { get; set; }
}
public class CompanyEmployee
{
public string Id { get; set; }
[ForeignKey("Company")]
public string CompanyId { get; set; }
public Company Company { get; set; }
public string EmployeeId { get; set; }
}
Now I want to map the Entities to Dtos defined as the following objects CompanyDto and its nested object EmployeeDto:
public class CompanyDto
{
[Required]
public string Id { get; set; }
[Required]
public string Name { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public IFormFile FormFile { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class EmployeeDto
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public List<RoleDto> Roles { get; set; }
}
My problem is the CompanyEmployee to EmployeeDto mapping.
How can I create a map that can take the property EmployeeId and map it to the Id property of EmployeeDto?
Currently, I have the following maps:
CreateMap<EmployeeDto, CompanyEmployee>(MemberList.Destination)
.ForMember(emp => emp.EmployeeId, opt => opt.MapFrom(ce => ce.Id));
CreateMap<CompanyDto, Company>(MemberList.Destination)
.ForMember(c => c.Employees.Select(e => e.CompanyId), opt => opt.MapFrom(cd => cd.Id));
CreateMap<Company, CompanyDto>(MemberList.Destination)
.ForMember(c => c.Id, opt => opt.MapFrom(cd => cd.Employees.First().CompanyId));
You want to create an AutoMapper Profile to configure each property mapping.
Create classes that inherit from Profile and put the configuration in the constructor.
For example:
public class EmployeeProfile : Profile
{
//Constructor
public EmployeeProfile()
{
//Mapping properties from CompanyEmployee to EmployeeDto
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmployeeId));
//Mapping properties from EmployeeDto to CompanyEmployee
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest => dest.EmployeeId, opt => opt.MapFrom(src => src.Id));
}
}
public class CompanyProfile : Profile
{
//Constructor
public CompanyProfile()
{
//Mapping properties from Company to CompanyDto
CreateMap<Company, CompanyDto>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees));
//Mapping properties from CompanyDto to Company
CreateMap<CompanyDto, Company>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees))
//Setting CompanyId
.AfterMap((src, dest) => {
foreach (var employee in dest.Employees)
{
employee.CompanyId = dest.Id;
}
});
}
}
AutoMapper Profile Configuration Documentation
Just create Profile and all properties which have the same name will mapped automatically. However, properties which do not have the same names, they should have custom mapping:
public class FromModelToDto : Profile
{
public FromModelToDto ()
{
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest.Id, opts => opts.MapFrom(model => model.EmployeeId))
}
}
UPDATE:
If you want to map from Dto to Model, then you should create another mapping class:
public class FromDtoToModel : Profile
{
public FromDtoToModel ()
{
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest.EmployeeId, opts => opts.MapFrom(model => model.Id))
}
}
You can read more about Automapper here.

EF One to Many Relationship on Composite Key

I'm having issues using Entity Framework (6.3) to retrieve a child collection of entities where the relationship uses a composite key. In the example below I'm trying to get the Sprints associated with a Plan, but the Sprints child collection keeps coming back empty.
// Returns no sprints
var queryUsingSelect = await _dbContext
.Plans
.Select(p => new
{
p,
p.Sprints
})
.ToListAsync();
// Returns a plan without sprints
var queryUsingInclude = await _dbContext
.Plans
.Include(p => p.Sprints)
.ToListAsync();
// Returns me all sprints
var allSprints = await _dbContext
.Plans
.SelectMany(p => p.Sprints)
.ToListAsync();
In the last query I've tested it using SelectMany which does return Sprints, but really I need to be able to do it using Include. I'm having the same issue with another collection in the same project so it seems to be an issue with my approach in general. Note that I have lazy loading turned off to prevent accidental n+1 queries.
Here's a stripped down version of my code:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
}
public class Aspiration
{
public int AspirationId { get; set; }
public string Title { get; set; }
}
public class Plan
{
public Plan()
{
Sprints = new List<Sprint>();
}
public int UserId { get; set; }
public int AspirationId { get; set; }
public virtual User User { get; set; }
public virtual Aspiration Aspiration { get; set; }
public virtual ICollection<Sprint> Sprints { get; set; }
}
public class Sprint
{
public int SprintId { get; set; }
public int UserId { get; set; }
public int AspirationId { get; set; }
public virtual Plan Plan { get; set; }
public virtual User User { get; set; }
public virtual Aspiration Aspiration { get; set; }
}
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
Property(t => t.Name)
.HasMaxLength(100)
.IsRequired();
}
}
public class AspirationMap : EntityTypeConfiguration<Aspiration>
{
public AspirationMap()
{
Property(t => t.Title)
.HasMaxLength(100)
.IsRequired();
}
}
public class PlanMap : EntityTypeConfiguration<Plan>
{
public PlanMap()
{
HasKey(s => new { s.UserId, s.AspirationId });
HasRequired(s => s.User)
.WithMany()
.HasForeignKey(s => s.UserId);
HasRequired(s => s.Aspiration)
.WithMany()
.HasForeignKey(s => s.AspirationId);
}
}
public class SprintMap : EntityTypeConfiguration<Sprint>
{
public SprintMap()
{
HasRequired(s => s.User)
.WithMany()
.HasForeignKey(s => s.UserId);
HasRequired(s => s.Aspiration)
.WithMany()
.HasForeignKey(s => s.AspirationId);
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.AspirationId, s.UserId });
}
}
public class MyDbContext : DbContext
{
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext()
: base(DbConstants.ConnectionStringName)
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Aspiration> Aspirations { get; set; }
public DbSet<Plan> Plans { get; set; }
public DbSet<Sprint> Sprints { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder
.Map(new UserMap())
.Map(new AspirationMap())
.Map(new PlanMap())
.Map(new SprintMap())
;
}
}
Well, I see some errors in your mapping.
The FK of Plan in Sprint must have the same order of the PK of Plan. So replace this:
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.AspirationId,s.UserId });
for this:
HasRequired(s => s.Plan)
.WithMany(d => d.Sprints)
.HasForeignKey(s => new { s.UserId, s.AspirationId });
After making those changes, I tried to run your code and everything worked fine.

One or more validation errors were detected during model generation

I have a problem when selecting a User.
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap ()
{
ToTable("USERS");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
Property(e => e.Name).HasColumnName("NAME");
Property(e => e.Password).HasColumnName("PASSWORD");
Property(e => e.Date).HasColumnName("DATE");
Property(e => e.Token).HasColumnName("TOKEN");
Property(e => e.Active).HasColumnName("ACTIVE");
HasRequired(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
HasMany(e => e.BranchesUsers).WithRequired().Map(e => e.MapKey("USER_ID"));
}
}
public class BranchMap : EntityTypeConfiguration<Branch>
{
public BranchMap ()
{
ToTable("BRANCHES");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
Property(e => e.Name).HasColumnName("NAME");
Property(e => e.Date).HasColumnName("DATE");
Property(e => e.Active).HasColumnName("ACTIVE");
HasRequired(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
HasMany(e => e.UsersBranches).WithRequired().Map(e => e.MapKey("BRANCH_ID"));
}
}
public class UserBranchMap : EntityTypeConfiguration<UserBranch>
{
public UserBranchMap()
{
ToTable("USERS_BRANCHES");
HasKey(e => e.Id);
Property(e => e.Id).HasColumnName("ID");
HasOptional(e => e.User).WithMany().Map(e => e.MapKey("USER_ID"));
HasOptional(e => e.Profile).WithMany().Map(e => e.MapKey("PROFILE_ID"));
HasOptional(e => e.Branch).WithMany().Map(e => e.MapKey("BRANCH_ID"));
HasOptional(e => e.Company).WithMany().Map(e => e.MapKey("COMPANY_ID"));
}
}
this is my model:
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public DateTime Date { get; set; }
public string Token { get; set; }
public Company Company { get; set; }
public List<UserBranch> BranchesUsers{ get; set; }
public bool Active{ get; set; }
}
public class Branch
{
public long Id { get; set; }
public Company Company{ get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
public List<UserBranch> UsersBranches { get; set; }
public bool Active { get; set; }
}
public class UserBranch
{
public long Id { get; set; }
public User User { get; set; }
public Profile Profile { get; set; }
public Branch Branch { get; set; }
public Company Company { get; set; }
}
when I perform a simple select the user model I get this error:
One or more validation errors were detected during model generation:
USER_ID: Name: Each property name in a type must be unique. Property name 'USER_ID' is already defined.
BRANCH_ID: Name: Each property name in a type must be unique. Property name 'BRANCH_ID' is already defined.

EF6 one-to-many fluent api with navigation properties

I'm trying to apply a one-to-many for my entities using EF6 and fluent API but keep getting this error:
EmailTemplate_Attachments_Source_EmailTemplate_Attachments_Target: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical.
These are my models:
public class EmailTemplate
{
public EmailTemplate()
{
Attachments = new List<EmailTemplateAttachment>();
}
public int EmailTemplateId { get; set; }
public int OperatorId { get; set; }
public EmailTemplateType MailType { get; set; }
public int LanguageId { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public string FromEmail { get; set; }
public DateTime CreationDate { get; set; }
public virtual ICollection<EmailTemplateAttachment> Attachments { get; set; }
}
public class EmailTemplateAttachment
{
public int EmailTemplateAttachmentId { get; set; }
public string ShortDescription { get; set; }
public string FilePath { get; set; }
public int EmailTemplateId { get; set; }
public virtual EmailTemplate EmailTemplate { get; set; }
}
These are the entities configurations
public EmailTemplateConfiguration()
{
ToTable("T_EMAILS");
HasKey(emailTemplate => new { emailTemplate.OperatorId, emailTemplate.MailType, emailTemplate.LanguageId });
HasMany(t => t.Attachments)
.WithRequired(a => a.EmailTemplate)
.HasForeignKey(a => a.EmailTemplateId);
Property(emailTemplate => emailTemplate.EmailTemplateId).HasColumnName("row_id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(emailTemplate => emailTemplate.OperatorId).HasColumnName("operator_id");
Property(emailTemplate => emailTemplate.MailType).HasColumnName("mail_type");
Property(emailTemplate => emailTemplate.LanguageId).HasColumnName("language_id");
Property(emailTemplate => emailTemplate.Subject).HasColumnName("subject");
Property(emailTemplate => emailTemplate.Content).HasColumnName("mail_content");
Property(emailTemplate => emailTemplate.FromEmail).HasColumnName("from_email");
Property(emailTemplate => emailTemplate.CreationDate).HasColumnName("insert_date");
}
public EmailTemplateAttachmentConfiguration()
{
ToTable("T_EMAILS_ATTACHMENTS");
HasKey(a => a.EmailTemplateAttachmentId);
HasRequired(a => a.EmailTemplate)
.WithMany(t => t.Attachments)
.HasForeignKey(a => a.EmailTemplateId);
Property(a => a.EmailTemplateAttachmentId).HasColumnName("attachment_id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(a => a.ShortDescription).HasColumnName("short_description");
Property(a => a.FilePath).HasColumnName("file_url");
Property(a => a.EmailTemplateId).HasColumnName("mail_id");
}
What am I doing wrong? I've tried so many wait to configure the foreign key and keep getting the same exception again and again
In your EmailTemplateConfiguration, you define the primary key for EmailTemplate is composite key:
HasKey(emailTemplate => new { emailTemplate.OperatorId, emailTemplate.MailType, emailTemplate.LanguageId });
But in EmailTemplateAttachmentConfiguration, you configure the dependent to use EmailTemplateId as foreign key, which is different from the primary key you defined above. Foreign key should be the same with principal table primary key.
Also, you define the relation between EmailTemplate and EmailTemplateAttachment twice (one in EmailTemplateConfiguration and one in EmailTemplateAttachmentConfiguration). It's redundant, one is enough

Categories

Resources