ASP.NET Core Navigation Properties For Mapping Table - c#

I have a table called contracts which has the id of entity 1 and entity 2. I use this to create links between entities, however I can't seem to get the definition of the dbContext correct. I can get the contracts to load but only one entity. When I view the contract either Entity1 is loaded or Entity2 is loaded but never both.
The models look like this:
public class Contract
{
public int Entity1ID { get; set; }
public int Entity2ID { get; set; }
public Entity Entity1 { get; set; }
public Entity Entity2 { get; set; }
}
My entity class looks like this
public class Entity
{
public int ID { get; set; }
public ICollection<Contract> Contracts1 { get; set; };
public ICollection<Contract> Contracts2 { get; set; };
}
This is my dbContext
public class EntityDbContext : DbContext
{
public EntityDbContext(DbContextOptions<EntityDbContext> options)
: base(options)
{
}
public DbSet<Models.Entity> Entities { get; set; }
public DbSet<Models.Contract> Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID);
}
}

From your model design , there is a self-referencing many-to-many relationship in Entity model , so you should change DeleteBehavior to Restrict(the default is cascade) like below:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID)
.OnDelete(DeleteBehavior.Restrict);
}
For loading related data , you could use the Include method that Sharif suggested above to specify related data to be included in query results
var result = _context.Contracts
.Include(c => c.Entity1)
.Include(c => c.Entity2)
.ToList();
Result

Related

How to get a list of children in a self-referencing table (Parent-Child) using EF Core?

I use the following Model to create a self-referencing table:
public class Part
{
[Key]
public long PartId { get; set; }
[Required]
public string PartName { get; set; }
public long? ParentPartId { get; set; }
public Part Parentpart { get; set; }
//Navigation properties
public ICollection<Part> ChildParts { get; set; } //For self-referencing
}
The Fluent API is:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//For parts self-referencing
modelBuilder.Entity<Part>(entity =>
{
entity.HasKey(x => x.PartId);
entity.Property(x => x.PartName);
entity.HasOne(e => e.Parentpart)
.WithMany(e => e.ChildParts)
.HasForeignKey(e => e.ParentPartId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
});
}
I can use the following code to get all the parents of a given part:
[HttpGet]
public async Task<IActionResult> GetParentPartsList(string partName)
{
var q = _sqlServerContext.Parts.Where(x => x.PartName == partName).Include(x => x.Parentpart);
while (q.Select(x => x.ParentPartId) == null)
{
q = q.ThenInclude(x => x.Parentpart);
}
q = q.ThenInclude(x => x.Parentpart);
return Ok(await q.ToListAsync());
}
I want to get a list of all children for a given part. I searched some threads such as this link, but they have a one-one relationship between child and parent while I need a one-many relationship. How can I achieve this?

How to join information whose relationship is repeated between two tables using EF Core

I have a double relationship between the Person and PersonCompany tables where a Person can be any individual or legal person or a Person who is registered as a Company.
When I need to fetch a Person (Person table) with Id 2 from the bank, the EF should return the People and PersonsCompany that relate to the Person table, but this is not happening ... I believe the problem occurs because the Person and PersonCompany properties are from same type as Person. This makes EF understand that they are the same thing and returns values ​​that do not match the related PersonCompany.
Do I have to do some sort of "Select" within the PersonsCompan navigation property? Does anyone know how to help me?
//Get value of table Person
public Pessoa GetById(int id)
{
return DbSet
.Include(pe => pe.Persons)
.ThenInclude(p => p.Person)
.Include(pe => pe.PersonsCompany)
.ThenInclude(pe => pe.PersonCmpany)
//(... other related tables )
.FirstOrDefault(x => x.PersonId == id);
}
public void Configure(EntityTypeBuilder<PersonEntity> builder)
{
builder.ToTable("PersonEntity");
builder.HasKey(pg => new { pg.PersonId, pg.PersonType});
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.Persons)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonId)
.HasColumnName("PersonId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.PersonType)
.HasColumnName("PersonTypeId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.IdGeneral)
.HasColumnName("IdGeneral")
.HasColumnType("integer")
.IsRequired();
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.PersonsCompany)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonCompanyId)
.HasColumnName("PersonCompanyId")
.HasColumnType("integer")
.IsRequired();
}
public class Person : Entity
{
public virtual ICollection<PersonEntity> Persons { get; private set; }
public virtual ICollection<PersonEntity> PersonsCompany { get; private set; }
}
public class PersonEntity
{
public int Id { get; private set; }
public int PersonId { get; private set; }
public int PersonCompanyId { get; private set; }
public virtual PersonType PersonType { get; private set; }
public virtual Person Person { get; private set; }
public virtual Person PersonCompany { get; private set; }
}
If I understand correctly, the problem is similar to Entity Framework Core: many-to-many self referencing relationship, so is the solution.
The confusion in your case comes from the collection navigation property names and their mappings to the reference navigation properties:
Person.Persons -> PersonEntity.Person
and
Person.PersonsCompany -> PersonEntity.PersonCompany
You should probably rename them like this:
Person.PersonCompanies -> PersonEntity.Person
and
Person.CompanyPersons -> PersonEntity.PersonCompany
so the other reference navigation property represents the intended link.
E.g.
Model:
public class Person : Entity
{
public virtual ICollection<PersonEntity> PersonCompanies { get; private set; }
public virtual ICollection<PersonEntity> CompanyPersons { get; private set; }
}
Relationship configuration:
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.PersonCompanies)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.CompanyPersons)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
Usage:
.Include(p => p.PersonCompanies)
.ThenInclude(pe => pe.PersonCompany)
.Include(p => p.CompanyPersons)
.ThenInclude(pe => pe.Person)

EF Core - Many to many relationship on a class

User-Friend relationship
I find an answer
Entity Framework Core: many-to-many relationship with same entity
and try like this.
Entitys:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
public class Friend
{
public int MainUserId { get; set; }
public User ManUser { get; set; }
public int FriendUserId { get; set; }
public User FriendUser { get; set; }
}
The fluent API:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.ManUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
When I Add-Migration, the error message is
Cannot create a relationship between 'User.Friends' and 'Friend.FriendUser', because there already is a relationship between 'User.Friends' and 'Friend.ManUser'.
Navigation properties can only participate in a single relationship.
What should I do? Or I should create an Entity FriendEntity:User?
The problem is that you can't have one collection to support both one-to-many associations. Friend has two foreign keys that both need an inverse end in the entity they refer to. So add another collection as inverse end of MainUser:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> MainUserFriends { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
And the mapping:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany(mu => mu.MainUserFriends)
.HasForeignKey(f => f.MainUserId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
One (or both) of the relationships should be without cascading delete to prevent multiple cascade paths.
It's not mandatory the second collection. You only need to left de .WithMany() empty like this:
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany()
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany()
.HasForeignKey(f => f.FriendUserId);
look at this : https://github.com/aspnet/EntityFramework/issues/6052

How to solve Entity Framework validation error?

I am creating a custom table and using Identity. I have a context for adding rows to this table aswell.
code:
public class UserRoleAccess
{
[Key]
public string Id { get; set; }
public string UserRoleId { get; set; }
[ForeignKey("Id")]
public virtual ApplicationRole ApplicationRole { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public int UserRoleAccessType { get; set; }
}
db context:
public class UserRoleAccessContext : DbContext
{
public UserRoleAccessContext() : base("DMConnection")
{
Database.SetInitializer<PersonContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public static UserRoleAccessContext Create()
{
return new UserRoleAccessContext();
}
public DbSet<UserRoleAccess> UserRoleAccess { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
//modelBuilder.Entity<IdentityUserLogin>().ToTable("Users").HasKey(e => e.UserId);
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>();
}
}
bindings:
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users);
modelBuilder.Entity<ApplicationUser>().HasRequired(p => p.Person)
.WithMany(b => b.Users)
.HasForeignKey(p => p.PersonId);
modelBuilder.Entity<UserRoleAccess>().HasRequired(p => p.ApplicationRole)
.WithMany(b => b.UserRoleAccess);
modelBuilder.Entity<UserRoleAccess>().HasRequired(p => p.ApplicationRole)
.WithMany(b => b.UserRoleAccess)
.HasForeignKey(p => p.UserRoleId);
}
Executing this code when I get an error:
public static void CreateUserRoleAccess(string name, int type, string id)
{
var userAccessContext = new UserRoleAccessContext();
var userRoleAccess = new UserRoleAccess();
userRoleAccess.UserRoleId = id;
userRoleAccess.IsActive = true;
userRoleAccess.UserRoleAccessType = type;
userRoleAccess.Name = name;
userAccessContext.UserRoleAccess.Add(userRoleAccess);
}
error:
One or more validation errors were detected during model generation:
Project.Models.IdentityUserRole: : EntityType 'IdentityUserRole' has
no key defined. Define the key for this EntityType. IdentityUserRoles:
EntityType: EntitySet 'IdentityUserRoles' is based on type
'IdentityUserRole' that has no keys defined.
I assume I am missing a binding somewhere but I can't figure out where?
EDIT:
I changed UserRoleAccess class. I changed the Id from string to long and
I tried this in TSQL
INSERT INTO UserRoleAccess (UserRoleId,IsActive, Name, UserRoleAccessType) VALUES('e5b2e76a-a106-4076-b219-6987411995e7', 1, 'TEST', 1)
It worked. Although Im still getting the same error when running it from my application
EDIT 2
added this in my bindings :
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
Still the same result
So funny thing is.
My second edit was actually correct. but for some reason it did not work when I migrated and updated db with code first.
I deleted all the tables and created them again via migrations and it work.
Although with the edit I had to put this code in both db contexts:
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });

Do we have to configure HasXXX and WithYYY on both entities (as opposed to only one of the entities) to establish a relationship?

Assume that I have two entities as follows:
class Blog
{
public Blog()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
// other properties go here
public ICollection<Post> Posts { get; set; }
}
class Post
{
public int Id { get; set; }
//other properties go here
public int FKBlogId { get; set; }
public Blog Blog { get; set; }
}
As each post belongs to a blog and each blog can have zero or more posts, the relationship is one to many.
I have not find an article, on the web, discussing whether or not we need to configure the relationship on both entities as follows.
class Context : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithRequired(p => p.Blog)
.HasForeignKey(p => p.FKBlogId);
modelBuilder.Entity<Post>()
.HasRequired(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.FKBlogId);
}
}
Do we have to configure the relationship on both entities? In other words, does one of the following suffice?
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithRequired(p => p.Blog)
.HasForeignKey(p => p.FKBlogId);
or
modelBuilder.Entity<Post>()
.HasRequired(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.FKBlogId);
I have tested using one of them and I cannot notice any difference between them. Could you give me a confirmation whether or not my understanding is correct?
One of the two is sufficient, which one doesn't matter.
Test project (more intended for Fred Wilson), Console project with reference to EntityFramework.dll (4.1 or 4.2):
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace EFMappingTest
{
class Blog
{
public Blog()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
// other properties go here
public ICollection<Post> Posts { get; set; }
}
class Post
{
public int Id { get; set; }
//other properties go here
public int FKBlogId { get; set; }
public Blog Blog { get; set; }
}
class Context : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Block 1
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithRequired(p => p.Blog)
.HasForeignKey(p => p.FKBlogId);
// End Block 1
// Block 2
modelBuilder.Entity<Post>()
.HasRequired(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.FKBlogId);
// End Block 2
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new Context())
{
var blogs = context.Blogs
.Select(b => new
{
BlogId = b.Id,
Posts = b.Posts.Select(p => new
{
Id = p.Id
})
})
.ToList();
}
}
}
}
This configuration, the configuration when commenting out "Block 2" and the configuration when commenting out "Block 1" - all three configurations create the same database schema and relationship (aside from the name of the FK constraint in the database which can be Blog_Posts or Posts_Blog).
The example query works in all three cases.

Categories

Resources