Multiple (optional) one to one Relationships with single navigation property - .net-core

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?

Related

Establishing one to one relations with Entity Framework 7

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.

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?

Many to many relation between Identity and custom table. EF7 - Code first

How can I make many to many relation between AspNetRoles from Identity 3.0 and my custom table? I want simple 3 table, with both PermissionId and RoleId, something like AspNetUsersRole. I have something like this:
public class Permission
{
public int PermissionId { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationRole> Roles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<Permission> Permissions { get; set; }
}
But when I want to add migration, I got error:
Unable to determine the relationship represented by navigation property 'ApplicationRole.Permissions' of type 'ICollection<Permission>'. Either manually configure the relationship, or ignore this property from the model.
EF Core (EF7) does not currently support many to many relationship without a join entity. (Reference)
So, what you should do is to create an entity class for the join table and mapping two separate one-to-many relationships. Like;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Regarding to this question answer, it can be done more easily like this-
class Photo
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
class PersonPhoto
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
class Person
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
Be sure to configure PersonPhoto with a composite key:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonPhoto>().HasKey(x => new { x.PhotoId, x.PersonId });
}
To navigate, use a Select:
// person.Photos
var photos = person.PersonPhotos.Select(c => c.Photo);
Add This namespace-
using Microsoft.AspNetCore.Identity;
public class Permission
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PermissionId { get; set; }
public string Name { get; set; }
public string UserIdFK { get; set; } //Foreign Key of Identity tbl
[ForeignKey("UserIdFK")]
public IdentityUser UserDetail { get; set; }
}
That's it, Happy coding :)

Insert into the junction table in EF 7

Since the many-to-many relationship is not supported in Entity Framework 7 yet,
I'm following work around in this link.
Here is the code from the link above:
class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Question
How can I associate a Tag to a Post?
In other words how can I add a row to the junction table?
Here's my answer to another SO question where the method I came up with is explained.
Create your Tag instance and add it to Tags. Do the same with the Post instance, initializing its PostTags navigation property, and add it to Posts.
Then create the PostTag instance, initializing it with both Post and Tag instances.
Finally call SaveChanges or SaveChangesAsync methods.
This will allow EF to properly manage ID properties before writing all the involved entities to the underlying DB.

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.

Resources