I try to use Entity Framework with code first and fluent api to implement a one to many relationship
I have two classes
namespace Mantenimiento.Business.Entities
{
public class Personal : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string Nombre { get; set; }
public string Apellido { get; set; }
public string Cuil { get; set; }
public string Legajo { get; set; }
[ForeignKey("Dni")]
public ICollection<ContactoEmergencia> Contacto { get; set; }
}
namespace Mantenimiento.Business.Entities
{
public class ContactoEmergencia : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string ApellidoNombre { get; set; }
public string Vinculo { get; set; }
public string Domicilio { get; set; }
public string telefono { get; set; }
public string Comentario { get; set; }
public int CreateUserId { get; set; }
[ForeignKey("Dni")]
public virtual Personal Personal { get; set; }
}
}
This is my dbContext
#region personals
modelBuilder.Entity<Personal>().ToTable("InfoPersonal").HasKey(t => t.Id);
modelBuilder.Entity<Personal>().Property(c => c.Id).UseSqlServerIdentityColumn().IsRequired();
modelBuilder.Entity<Personal>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.Deleted).HasDefaultValue(false);
modelBuilder.Entity<Personal>().HasMany<ContactoEmergencia>(c => c.Contacto).WithOne(p => p.Personal).HasForeignKey(s => s.Dni);
#endregion
#region contactoEmergencias
modelBuilder.Entity<ContactoEmergencia>().ToTable("InfoEmergencia").HasKey(d => d.Dni);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.Deleted).HasDefaultValue(false);
#endregion
And my query is
return await _context.personals
.Include(c => c.Contacto)
.Where(p => p.Deleted == false)
.OrderBy(s => s.Apellido)
.ToListAsync(
);
But the properties is always empty.
i need to relate Personal.Di with Contacto.Dni, i had to change the key?
You should remove ForeignKey attribute from Personal entity. In one to many relationship only child entity could accept ForeignKey.
Related
Let's assume that Administrator, Purchaser and Supplier have User base type and remaining models look following:
public class Vendor
{
public int VendorId { get; set; }
public List<Supplier> Suppliers { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public Administrator Admin { get; set; }
public List<Purchaser> Purchasers { get; set; }
public Vendor Vendor { get; set; }
}
Now I would like to create a UserTask table that contains IDs of all users of the Task: an Admin, Purchasers and Suppliers of the Vendor in column User and their Tasks IDs in column Task.
How could I configure such setup in Fluent API?
Edit:
I created additional entity UserTask that consists of IDs and navigation properties:
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int TaskId { get; set; }
public Task Task { get; set; }
//some other needed properties
}
And tried to configure models like this:
modelBuilder.Entity<UserTask>(ut =>
{
ut.HasKey(x => new { x.UserId, x.TaskId });
ut.HasOne(u => u.User).WithMany()
.HasForeignKey(u => u.UserId)
.OnDelete(DeleteBehavior.Cascade);
ut.HasOne(t => t.Task).WithMany()
.HasForeignKey(t => t.TaskId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Task>(t =>
{
t.HasMany(p => p.Purchasers).WithMany(p => p.Tasks);
t.HasOne(a => a.Administrator).WithMany(); //adding a => a.Task expression in parameter throws error that the relationship is already defined
t.HasMany(s => s.Vendors.Suppliers).WithMany(s => s.Tasks); //throws error
});
And it fails because HasMany(s => s.Vendors.Suppliers) i not a valid member access expression. Is there any way to overcome this issue?
Considering the relationships in these tables, add a property so that Fluent API can reference the relationship. About the specific modelbuilder.
modelBuilder.Entity<Supplier>()
.HasOne(x => x.vendor)
.WithMany(y => y.Suppliers);
modelBuilder.Entity<Administrator>()
.HasOne(a => a.tasks)
.WithOne(t => t.Admin)
.HasForeignKey<Administrator>(f=>f.AdministratorId);
modelBuilder.Entity<Vendor>()
.HasOne(a => a.tasks)
.WithOne(t => t.Vendor)
.HasForeignKey<Vendor>(f=>f.VendorId);
The model need to be redesigned as this.
public class User
{
public int id { get; set; }
public string Property { get; set; }
}
public class Vendor
{
public int VendorId { get; set; }
public List<Supplier> Suppliers { get; set; }
public Tasks tasks { get; set; }
}
public class Tasks
{
[Key]
public int TaskId { get; set; }
public Administrator Admin { get; set; }
public List<Purchaser> Purchasers { get; set; }
public Vendor Vendor { get; set; }
}
public class Supplier:User
{
public int SupplierId { get; set; }
public string SupplierProperty { get; set; }
public Vendor vendor { get; set; }
}
public class Administrator:User
{
public int AdministratorId { get; set; }
public string adminProperty { get; set; }
public Tasks tasks { get; set; }
}
public class Purchaser:User
{
public int PurchaserId { get; set; }
public string purProperty { get; set; }
public Tasks tasks { get; set; }
}
In my app one case can have many companies.
My models:
public class Case
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CaseCompany> CaseCompanies { get; set; }
}
public class CaseInput
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public IList<CaseCompanyInput> CaseCompanyInputs { get; set; }
}
public class Company
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CompanyInput
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
}
public class CaseCompany
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
public class Case Case { get; set; }
public class Company Company { get; set; }
}
public class CaseCompanyInput
{
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
public class CaseInput CaseInput { get; set; }
public class CompanyInput CompanyInput { get; set; }
}
AutoMapperProfiles.cs:
// In Startup.cs: services.AddAutoMapper(typeof(AutoMapperProfiles));
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<Case, CaseInput>().ReverseMap();
CreateMap<CaseCompany, CaseCompanyInput>().ReverseMap();
CreateMap<Company, CompanyInput>().ReverseMap();
}
}
EditCase.cs:
private readonly DBContext _dbContext;
private readonly IMapper _mapper;
[BindProperty]
public CaseInput CaseInput { get; set; }
public EditCase(DBContext dbContext, IMapper mapper)
{
_dbContext = dbContext;
_mapper = mapper;
}
public async Task<IActionResult> OnGetAsync(Guid caseId)
{
var getCase = await _dbContext.Cases.Include(x => x.CaseCompanies).ThenInclude(x => x.Company).FirstOrDefaultAsync(x => x.Id == caseId);
CaseInput = _mapper.Map<CaseInput>(getCase);
Console.WriteLine(getCase.CaseCompanies[0].Company.Name) // gets the company name
Console.WriteLine(CaseInput.CaseCompanyInputs[0].CompanyInput.Name) // CaseInput.Name is not null but CaseCompanyInputs is null
return Page();
}
I've also tried:
CaseInput = await _dbContext.Cases.ProjectTo<CaseInput>(_mapper.ConfigurationProvider).FirstOrDefaultAsync(x => x.Id == caseId);
and
CaseInput = await _dbContext.Cases.Include(x => x.CaseCompanies).ThenInclude(x => x.Company).ProjectTo<CaseInput>(_mapper.ConfigurationProvider).FirstOrDefaultAsync(x => x.Id == caseId);
with the same result: CaseCompanyInputs is null.
My best guess is that it's an error in the relations of the input models, but I just can't see it. I believe I've followed the naming conventions to make the relations right.
Any help would be appreciated.
For automapping to work, you have to rename ur destination property name the same as the source property name
public class CaseInput
{
public Guid Id { get; set; }
public string Name { get; set; }
//public IList<CaseCompanyInput> CaseCompanyInputs { get; set; }
public IList<CaseCompanyInput> CaseCompanies { get; set; }
}
public class CaseCompanyInput
{
public Guid Id { get; set; }
public Guid CaseId { get; set; }
public Guid CompanyId { get; set; }
//public CaseInput CaseInput { get; set; }
public CaseInput Case { get; set; }
//public CompanyInput CompanyInput { get; set; }
public CompanyInput Company { get; set; }
}
If you don't want to change the property names, change ur configuration to the following.
CreateMap<Case, CaseInput>()
.ForMember(dest => dest.CaseCompanyInputs, opt => opt.MapFrom(src => src.CaseCompanies));
CreateMap<CaseCompany, CaseCompanyInput>()
.ForMember(dest => dest.CaseInput, opt => opt.MapFrom(src => src.Case))
.ForMember(dest => dest.CompanyInput, opt => opt.MapFrom(src => src.Company));
I have an entity with some optional relationships and I'm doing a GetAllIncluding(someProperties) but the navigation properties keeps in null when the GetAll is done.
All relation in the include (Cliente, ClienteFuturo) keeps in null, and always almost one of them has a value on ClienteId or ClienteFuturoId
Here is my GetAll Method:
public override Task<PagedResultDto<SolicitudPrestamoDto>> GetAll(PagedAndSortedRequest input)
{
var lista = new List<SolicitudPrestamo>();
var query = Repository.GetAllIncluding(x => x.ClienteFuturo, x => x.Cliente);
query = CreateFilteredQuery(input);
query = ApplySorting(query, input);
query = FilterHelper<SolicitudPrestamo>.FilerByProperties(input.FilterProperties, query);
lista = query
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.ToList();
var result = new PagedResultDto<SolicitudPrestamoDto>(query.Count(), ObjectMapper.Map<List<SolicitudPrestamoDto>>(lista));
return Task.FromResult(result);
}
Here is the entity relation configuration:
entidad.HasOne(e => e.Cosolicitante)
.WithMany()
.HasForeignKey(e => e.CosolicitanteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_Cosolicitante")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.Cliente)
.WithMany()
.HasForeignKey(e => e.ClienteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_Cliente")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.CosolicitanteCliente)
.WithMany()
.HasForeignKey(e => e.CosolicitanteClienteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_CosolicitanteCliente")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.ClienteFuturo)
.WithMany()
.HasForeignKey(e => e.ClienteFuturoId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_ClienteFuturo")
.OnDelete(DeleteBehavior.Restrict);
Here is my entity:
public class SolicitudPrestamo : AuditedEntity<int>
{
public string Identificador { get; set; }
public int CantidadCuotas { get; set; }
public double Monto { get; set; }
public string FormaPago { get; set; }
public DateTime Fecha { get; set; }
public string Proposito { get; set; }
public string Referencia { get; set; }
public EstadoSolicitud Estado { get; set; }
public int SucursalId { get; set; }
public virtual Sucursal Sucursal { get; set; }
public int? ClienteId { get; set; }
public virtual Cliente Cliente { get; set; }
public int? CosolicitanteClienteId { get; set; }
public virtual Cliente CosolicitanteCliente { get; set; }
public int? ClienteFuturoId { get; set; }
public virtual ClienteFuturo ClienteFuturo { get; set; }
public int ClasificacionPrestamoId { get; set; }
public virtual ClasificacionPrestamo ClasificacionPrestamo { get; set; }
public int? OficialNegocioId { get; set; }
public virtual OficialNegocio OficialNegocio { get; set; }
public int? CobradorPrestamoId { get; set; }
public virtual CobradorPrestamo CobradorPrestamo { get; set; }
public int? CosolicitanteId { get; set; }
public virtual ClienteFuturo Cosolicitante { get; set; }
public IEnumerable<GarantiaPrestamoSolicitud> ListaGarantiaPrestamo { get; set; }
public IEnumerable<ReferenciaPrestamo> ListaReferencias { get; set; }
public List<GarantiaPrestamo> ListaGarantias { get; set; }
}
Sorry for my English.
protected override IQueryable<SolicitudPrestamo> CreateFilteredQuery(PagedAndSortedRequest input)
{
return Repository.GetAll().
WhereIf(!input.Filter.IsNullOrWhiteSpace(), x =>
x.Identificador.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.FormaPago.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.Proposito.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.Referencia.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase)
);
}
Thanks illia-popov the problem is that in the CreatedFilteredQuery Method I forget to do the GetAllIncluding
Thanks for help.!
I'm trying to make a simple app to try Entity Framework Core, but i a have problem with setting up relations between entities. My entities:
public class Card
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Adress { get; set; }
public DateTime DoB { get; set; }
public DateTime DoS { get; set; }
public User Portal { get; set; }
public List<Reservation> Res { get; set; }
}
public class Doctor
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public TimeSpan Start_Working { get; set; }
public TimeSpan End_Working { get; set; }
public List<Reservation> Reservations { get; set; }
public int SpecID { get; set; }
public Spec Spec { get; set; }
}
public class Reservation
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime DoR { get; set; }
public string Info { get; set; }
public int CardID { get; set; }
public Card Card_Nav_R { get; set; }
public int DoctorID { get; set; }
public Doctor Doctor { get; set; }
}
public class Spec
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public List<Doctor> Doctors { get; set; }
}
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public int CardID { get; set; }
public Card Card { get; set; }
}
And a configuration class where i tried to set up relations:
class ApplicationContext:DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Card> Cards { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Doctor> Doctors { get; set; }
public DbSet<Spec> Specs { get; set; }
public ApplicationContext()
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder ModelBuilder)
{
ModelBuilder.Entity<User>().HasKey(u => u.Id);
ModelBuilder.Entity<Card>().HasKey(c => c.Id);
ModelBuilder.Entity<Doctor>().HasKey(d => d.Id);
ModelBuilder.Entity<Spec>().HasKey(s => s.Id);
ModelBuilder.Entity<Reservation>().HasKey(r => r.Id);
ModelBuilder.Entity<User>().Property(u => u.Email).IsRequired();
ModelBuilder.Entity<User>().Property(u => u.Password).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Name).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Surname).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.DoB).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Adress).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Name).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Surname).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Email).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Start_Working).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.End_Working).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Info).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Card_Nav_R).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Doctor).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.DoR).IsRequired();
ModelBuilder.Entity<Spec>().Property(s => s.Name).IsRequired();
ModelBuilder.Entity<Doctor>().HasOne<Spec>(d=>d.Spec).WithMany(s => s.Doctors).HasForeignKey(d => d.SpecID);
ModelBuilder.Entity<User>().HasOne<Card>(u => u.Card).WithOne(c => c.Portal).HasForeignKey<User>(u => u.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Card>(r => r.Card_Nav_R).WithMany(c => c.Res).HasForeignKey(r => r.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Doctor>(r => r.Doctor).WithMany(d => d.Reservations).HasForeignKey(r => r.DoctorID);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Simple_Try;Trusted_Connection=True;");
}
}
So, when i tried to add migration or add something to database i saw this error:
System.InvalidOperationException: 'The property or navigation 'Spec' cannot be added to the entity type 'Doctor' because a property or navigation with the same name already exists on entity type 'Doctor'.'
I really don't know how to fix this, i tried to use annotations instead of Fluent API, but had the same result.
The cause of the exception is the following line:
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
because Doctor.Spec is a navigation property
public class Doctor
{
// ...
public Spec Spec { get; set; }
}
and navigation properties cannot be configured via Property fluent API.
So simply remove that line. Whether reference navigation property is required or optional is controlled via relationship configuration. In this case
ModelBuilder.Entity<Doctor>()
.HasOne(d => d.Spec)
.WithMany(s => s.Doctors)
.HasForeignKey(d => d.SpecID)
.IsRequired(); // <--
although the IsRequired is automatically derived from the FK property type - since SpecID is non nullable, then the relationship is required.
For more info, see Required and Optional Properties and Required and Optional Relationships documentation topics.
I cant seem to figure out why my navigation property is not getting built by my include statement.
Here is my method:
public async Task<IHttpActionResult> GetCompanies(string id)
{
DbContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
var company = await DbContext.Companies.Where(x => x.Id.ToString() == id).Include(x => x.StartelAccounts).FirstOrDefaultAsync();
if (company != null)
{
return Ok(this.TheModelFactory.Create(company));
}
return NotFound();
}
When I test the SQL from the debug log I get all the fields and values for both objects.
Here are the models:
public class CompanyGroup
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime FirstBillingDate { get; set; }
[Required]
public int TermLength { get; set; }
public virtual ICollection<ApplicationUser> Members { get; set; }
public virtual ICollection<AccountStartel> StartelAccounts { get; set; }
public CompanyGroup()
{
Members = new HashSet<ApplicationUser>();
StartelAccounts = new HashSet<AccountStartel>();
}
}
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public virtual CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
Company = new CompanyGroup();
CompanyId = Guid.Empty;
UsageReports = new List<UsageReport>();
}
}
EF Fluent API
modelBuilder.Entity<AccountStartel>()
.HasRequired<CompanyGroup>(x => x.Company)
.WithMany(x => x.StartelAccounts)
.HasForeignKey(x => x.CompanyId);
modelBuilder.Entity<AccountStartel>()
.Property(p => p.DbId)
.IsRequired()
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(
new System.ComponentModel.DataAnnotations.Schema.IndexAttribute("IX_StartelDbId", 1) { IsUnique = true }));
Can anyone see what im missing here?
Could it have to do with setting Company and/or CompanyId in the
AccountStartel constructor? Does it work if you remove those lines? –
Peter
Initializing the navigation properties to a default value caused EF to not load them correctly.
Here is the updated model which does work now
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
UsageReports = new List<UsageReport>();
}
}