Establishing one to one relations with Entity Framework 7 - asp.net

Having the following parent class:
[Table("smart_recharge_registro")]
public class SmartRechargeRegistro
{
[Key]
public int id { get; set; }
public SmartRechargeRequest request { get; set; }
public SmartRechargeProceso proceso { get; set; }
public SmartRechargeResponse response { get; set; }
}
Which in turn references the following child classes:
[Table("smart_recharge_request")]
public class SmartRechargeRequest
{
public String nombreDeUsuario { get; set; }
public String passwordDeUsuario { get; set; }
public String msisdnSuscriptor { get; set; }
}
and:
[Table("smart_recharge_proceso")]
public class SmartRechargeProceso
{
[Key]
public int id { get; set; }
public String carrierId { get; set; }
public String cliente { get; set; }
public String network { get; set; }
}
and lastly:
[Table("smart_recharge_response")]
public class SmartRechargeResponse
{
public Boolean responseSuccess { get; set; }
public int responseCode { get; set; }
public String? responseDetails { get; set; }
}
The Add-Migration and Update-Database command execute without problems. However, when I try to save
await _repository.RegistroColeccion.AddAsync(registro);
await _repositorio.SaveChangesAsync();
I get the following error:
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Cannot add
or update a child row: a foreign key constraint fails
(beservicebroker_dev.registro_eventos_srdotnet, CONSTRAINT
FK_registro_eventos_srdotnet_SmartRechargeProceso_procesoid FOREIGN
KEY (procesoid) REFERENCES smartrechargeproceso (id) O)
To solve the problem, I tried to create one-to-one relationships following this tutorial
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.request)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeRequest>(r => r.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.proceso)
.WithOne(p => p.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeProceso>(p => p.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.response)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeResponse>(r => r.id);
Inside SmartRechargeRequest, SmartRechargeProceso and SmartRechargeResponse, added the following:
[JsonIgnore]
public SmartRechargeRegistro SmartRechargeRegistro { get; set; }
Also added inside SmartRechargeRequest and SmartRechargeResponse an id
[Key]
public int id { get; set; }
I'm still unable to test the endpoint because the SmartRechargeRequest and SmartRechargeResponse are completely disfigured in the swagger (even if the [JsonIgnore] or [IgnoreDataMember] annotations are set) due to the presence of that SmartRechargeRegistro object.
I'm pretty sure my solution is misguided and I'm getting the process completely wrong.
What would be the proper way to map one-to-one relationships for this case? Any help will be appreciated.
Please note that in reality, these classes are huge (dozens of properties), so it's not possible to merge all of them on a single table.

Related

Multiple (optional) one to one Relationships with single navigation property

I'm trying to configure a relationship between two entities where the parent can have multiple (named) childs from the same type while the child should only have one generic parent.
Based on the Blog/Posts example I'm trying to configure the following. Blogposts aren't a good example but in my application I have 5 fixed child entities.
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public int? FirstPostId { get; set; }
public Post FirstPost { get; set; }
public int? SecondPostId { get; set; }
public Post SecondPost { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
I tried to configure this like:
modelBuilder
.Entity<Blog>()
.HasOne(_ => _.FirstPost)
.WithOne()
.HasForeignKey<Blog>(_ => _.FirstPostId);
modelBuilder
.Entity<Blog>()
.HasOne(_ => _.SecondPost)
.WithOne()
.HasForeignKey<Blog>(_ => _.SecondPostId);
modelBuilder
.Entity<Post>()
.HasOne(_ => _.Blog)
.WithOne()
.HasForeignKey<Post>(_ => _.BlogId);
In my real world problem a Post additionally does not need to belong to a Blog and all foreign keys are composite keys but that should be no problem.
Configured like that FirstPost and SecondPost are recognized but Post.Blog/Post.BlogId is always null.
Any ideas how to solve this?

EFCore Many-To-Many relationship with the same table on the ends

I want to track referrals for users. There are two entities classes.
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public ICollection<Referral> Referrals { get; set; }
public ICollection<Referral> Referrees { get; set; }
}
public class Referral
{
public long ReferrerId { get; set; }
public long ReferreeId { get; set; }
public virtual User Referrer { get; set; }
public virtual User Referree { get; set; }
}
I defined relationships between them through fluent API on model creating (for the Referral entity)
builder.HasOne(r => r.Referrer).WithMany(u => u.Referrals).HasForeignKey(r => r.ReferrerId);
builder.HasOne(r => r.Referree).WithMany(u => u.Referrees).HasForeignKey(u => u.ReferreeId);
But when I try adding a migration, I get the
Unable to determine the relationship represented by navigation
property 'Referral.Referrer' of type 'User'. Either manually configure
the relationship, or ignore this property using the '[NotMapped]'
attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
error.
What am I doing wrong?

Introducing FOREIGN KEY constraint with ON DELETE NO ACTION doesn't working

I have 4 tables with between them foreign keys as:
Classification - Classification level - Classification value - Classification language
on add-migration it is ok, but when running update-database I get the error:
Introducing FOREIGN KEY constraint 'FK_dbClassificationValue_dbClassificationLevel_ClassificationLevelId' on table 'dbClassificationValue' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
The tables as mentioned in the error are defined as:
public class SuClassificationLevelModel
{
public int Id { get; set; }
public int ClassificationId { get; set; }
public int Sequence { get; set; }
public bool DateLevel { get; set; }
public bool OnTheFly { get; set; }
public bool Alphabetically { get; set; }
public bool CanLink { get; set; }
public bool InDropDown { get; set; }
public Guid CreatorId { get; set; }
public Guid ModifierId { get; set; }
public DateTime ModifiedDate { get; set; }
public DateTime CreatedDate { get; set; }
public virtual SuClassificationModel Classification { get; set; }
public virtual ICollection<SuClassificationLevelLanguageModel> ClassificationLevelLanguages { get; set; }
public virtual ICollection<SuClassificationValueModel> ClassificationValues { get; set; }
}
and
public class SuClassificationValueModel
{
public int Id { get; set; }
public int ClassificationLevelId { get; set; }
public int ParentValueId { get; set; }
public DateTimeOffset DateFrom { get; set; }
public DateTimeOffset DateTo { get; set; }
public virtual SuClassificationLevelModel ClassificationLevel { get; set; }
public virtual ICollection<SuClassificationValueLanguageModel> ClassificationValueLanguages { get; set; }
}
I have added the delete behavior line in my DBContect class as:
modelBuilder.Entity<SuClassificationValueModel>()
.HasOne(u => u.ClassificationLevel)
.WithMany(u => u.ClassificationValues)
.HasForeignKey(u => u.ClassificationLevelId)
.OnDelete(DeleteBehavior.Restrict);
Also, I have put this on the foreign keys between all tables of the cascading structure.
modelBuilder.Entity<SuClassificationLevelModel>()
.HasOne(u => u.Classification)
.WithMany(u => u.ClassificationLevels)
.HasForeignKey(u => u.ClassificationId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<SuClassificationValueModel>()
.HasOne(u => u.ClassificationLevel)
.WithMany(u => u.ClassificationValues)
.HasForeignKey(u => u.ClassificationLevelId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<SuClassificationValueLanguageModel>()
.HasOne(u => u.ClassificationValue)
.WithMany(u => u.ClassificationValueLanguages)
.HasForeignKey(u => u.ClassificationValueId)
.OnDelete(DeleteBehavior.Restrict);
Further, I have tried to set it for all foreign keys with:
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
As I don't need the cascading delete behavior.
After these different tries I did again an add-migration and update-database. But the error is still the same.
Any suggestions?
Finally, I went into the migration files and changed everywhere from cascade to restrict. As I don't want to use cascade anyway. And now it works.

Code-first database mapping, table missing (wasn't created) for many-to-many relationship

I am building ASP.NET webforms application using Entity Framework 6.1, with code-first approach to generate database. I have two tables, Product and Tags, in many-to-many relationship. Classes are below:
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
}
I want two junction tables out of this relationship ProductTags and ProductTagsTradeFor. So I overrided OnModelCreating of WebsiteDbContext.
modelBuilder.Entity<Product>().HasMany<Tag>(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTags");
});
modelBuilder.Entity<Product>().HasMany<Tag>(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTradeForTags");
});
After running the application, database was created and table ProductTradeForTags is present but table ProductTags was missing.
What is the problem and how do I fix it so both tables are created?
You can't share the navigation properties. You will need to add a second set of navigation collections to each:
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Tag> TradeForTags { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public virtual ICollection<Product> TradeForProducts{ get; set; }
}
Then
modelBuilder.Entity<Product>().HasMany(s => s.Tags).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTags");
});
modelBuilder.Entity<Product>().HasMany(s => s.TradeForTags).WithMany(c => c.TradeForProducts)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("TagId");
cs.ToTable("ProductTradeForTags");
});
Your model require to navigate from a Tag to Products and from a Product to Tags.
In this case one association table is enough.
EF should raise an exception but it simply ignores the first configuration.

Many to many relationships in EF5 Code First, how can I specify table name?

I'm quite new to EF, and I'm not really sure how to do this.
I have a many-to-many relationship, exactly like this:
When I try to add a resource (Recurso) to a profile (Perfil), I get the following error:
Invalid object name 'dbo.RecursoPerfils
Where the hell did RecursoPerfils come from?
How can I specify (preferably through attribute annotation) the table name for this relationship?
See the models below:
[Table("Perfil")]
public class Perfil
{
public Perfil()
{
this.Usuarios = new List<Usuario>();
this.Recursos = new List<Recurso>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int IdPerfil { get; set; }
[Required]
public string Descricao { get; set; }
public virtual ICollection<Usuario> Usuarios { get; set; }
public virtual ICollection<Recurso> Recursos { get; set; }
}
[Table("Recurso")]
public class Recurso
{
public Recurso()
{
this.Perfis = new List<Perfil>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int IdRecurso { get; set; }
[Required]
public string NomeRecurso { get; set; }
[Required]
public string Descricao { get; set; }
public virtual ICollection<Perfil> Perfis { get; set; }
}
You need to use Fluent API to configure the table name of the join table.
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Perfil>()
.HasMany(p => p.Recursos)
.WithMany(r => r.Perfis)
.Map(mc =>
{
mc.MapLeftKey("IdPerfil");
mc.MapRightKey("IdRecurso");
mc.ToTable("PerfilRecurso");
});
}
}
You can go through this Fluent API relationship mapping tutorial for more info

Resources