EF table splitting with table AspNetUsers - asp.net

I use two classes.
public partial class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<IntRole> Roles { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
public partial class ApplicationUser : IdentityUser<int, IntUserLogin, IntUserRole, IntUserClaim>
{
public virtual User User { get; set; }
}
I want to map both classes to table AspNetUsers.
I configured all fields in this table to make them optional.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().HasOptional(a => a.User).WithMany().HasForeignKey(a => a.Id);
modelBuilder.Entity<ApplicationUser>().HasRequired(a => a.User).WithRequiredDependent(s => s.ApplicationUser);
modelBuilder.Entity<ApplicationUser>().Property(a => a.EmailConfirmed).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.PhoneNumber).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.PhoneNumberConfirmed).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.TwoFactorEnabled).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.LockoutEnabled).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.AccessFailedCount).IsOptional();
modelBuilder.Entity<ApplicationUser>().Property(a => a.UserName).IsOptional();
modelBuilder.Entity<User>().ToTable("AspNetUsers");
}
I make automatic migration and the result is successful.
When I want to add single User defined by class User in controller.
context.SpecialUser.Add(new Models.User { FirstName = "Halo", LastName = "Halo2" });
context.SaveChanges();
I got the following info.
Invalid data encountered. A required relationship is missing. Examine StateEntries to determine the source of the constraint violation.
What relationship should I define?
Result of InnerException.

Related

One or more validation errors were detected during model generation Entity Framework

Hi I have a problem with relationship between my tables. When i try to get values from table i have an error:
OrderDetails_Order_Target_OrderDetails_Order_Source: : The number of
properties in the Dependent and Principal Roles in a relationship
constraint must be identical. OrderDetails_Processor_Source: :
Multiplicity is not valid in Role 'OrderDetails_Processor_Source' in
relationship 'OrderDetails_Processor'. Because the Dependent Role
refers to the key properties, the upper bound of the multiplicity of
the Dependent Role must be '1'.
OrderDetails_Processor_Target_OrderDetails_Processor_Source: : The
number of properties in the Dependent and Principal Roles in a
relationship constraint must be identical.
My classes and relationship look that:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Users>().HasKey(x => x.ID_User);
modelBuilder.Entity<Processor>().HasKey(x => x.Product_ID);
modelBuilder.Entity<GPU>().HasKey(x => x.Product_ID);
modelBuilder.Entity<Orders>().HasKey(x => new { x.Order_ID, x.User_ID });
modelBuilder.Entity<OrderDetails>().HasKey(x => new { x.Order_ID, x.Product_ID });
modelBuilder.Entity<OrderDetails>().HasRequired(x => x.Processor).WithMany(x => x.OrderDetails).HasForeignKey(x => new { x.Product_ID, x.Order_ID });
modelBuilder.Entity<OrderDetails>().HasRequired(x => x.GPU).WithMany(x => x.OrderDetails);
modelBuilder.Entity<OrderDetails>().HasRequired(x => x.Order).WithMany(x => x.OrderDetails);
modelBuilder.Entity<Orders>().HasMany(x => x.OrderDetails).WithRequired(x => x.Order);
modelBuilder.Entity<Orders>().HasRequired(x => x.User).WithMany(x => x.Orders);
modelBuilder.Entity<Processor>().HasMany(x => x.OrderDetails).WithRequired(x => x.Processor).HasForeignKey(x => new { x.Product_ID, x.Order_ID });
modelBuilder.Entity<GPU>().HasMany(x => x.OrderDetails).WithRequired(x => x.GPU);
modelBuilder.Entity<Users>().HasMany(x => x.Orders).WithRequired(x => x.User);
}
public class OrderDetails
{
public int Order_ID { get; set; }
public int Product_ID { get; set; }
public int Quantity { get; set; }
[ForeignKey(nameof(Product_ID))]
public Processor Processor { get; set; }
[ForeignKey(nameof(Product_ID))]
public GPU GPU { get; set; }
[ForeignKey(nameof(Order_ID))]
public Orders Order { get; set; }
}
public class Processor
{
[Key]
public int Product_ID { get; set; }
public string Title { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public ICollection<OrderDetails> OrderDetails { get; set; }
}
And here i get an error
public class EfProcessorContext : IProcessorRepository
{
private EfDbContext context = new EfDbContext();
public IEnumerable<Processor> Processors
{
get
{
return context.Processors.Include(x => x.OrderDetails);
}
}
}
What wrong I do?
The reason may be for the error is incorrectly configured relations in your model.
How to fix: The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical?
https://entityframework.net/knowledge-base/28699205/-the-number-of-properties-in-the-dependent-and-principal-roles-in-a-relationship-constraint-must-be-identical--issue-in-entity-framework
"The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical" issue in entity framework

Introducing FOREIGN KEY constraint 'FK_Product_User_UserId' on table 'Product' may cause cycles or multiple cascade paths

i cannot create database with ef core
error : Introducing FOREIGN KEY constraint 'FK_Product_User_UserId' on table 'Product' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
this is my product class
{
public Product()
{
}
public Guid UserId { get; set; }
public Guid CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string PhotoPath { get; set; }
public decimal Price { get; set; }
public Category Category { get; set; }
public User User { get; set; }
}
and
this is my user class
{
public User()
{
Payments = new HashSet<Payment>();
Categories = new HashSet<Category>();
Products = new HashSet<Product>();
}
public string Username { get; set; }
public Guid Password { get; set; }
public ICollection<Payment> Payments { get; set; }
public ICollection<Category> Categories { get; set; }
public ICollection<Product> Products { get; set; }
}
its mapping class
{
public ProductMap()
{
}
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.UserId).IsRequired();
builder.Property(x => x.CreatedDate).IsRequired();
builder.Property(x => x.Description).HasMaxLength(500);
builder.Property(x => x.IsActive).IsRequired();
builder.Property(x => x.Name).HasMaxLength(500).IsRequired();
builder.Property(x => x.PhotoPath).HasMaxLength(4000);
builder.Property(x => x.Price).HasColumnType("decimal(10,3)").IsRequired();
builder.HasOne(x => x.Category).WithMany(x => x.Products).HasForeignKey(x => x.CategoryId);
builder.HasOne(x => x.User).WithMany(x => x.Products).HasForeignKey(x => x.UserId);
}
}
and i cannot create database cause error like this posts title.
what can i do?
Thanks.
You are saying that each user has many Categories and each Category has many Products so you have to remove this line from the User class since it causing cycle path
public ICollection<Product> Products { get; set; }
And also fix the last line of the Configure method:
builder.HasOne(x => x.User).WithMany(x => x.Categories).HasForeignKey(x => x.UserId);

How to define two FK in the same table?

I have a table called User which inherit the properties from IdentityUser, inside that table I added a reference to the UserFriendship table which need to store all the user friendship:
public class User : IdentityUser
{
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
public virtual ICollection<UserFriendship> UserFriendship { get; set; }
}
Essentially the UserFriendship contains two users, who are those who have a common friendship, this is the model definition:
public class UserFriendship
{
public int Id { get; set; }
[Key, ForeignKey("UserA")]
public string UserAId { get; set; }
public User UserA { get; set; }
[Key, ForeignKey("UserB")]
public string UserBId { get; set; }
public User UserB { get; set; }
[Required]
public DateTime DateTime { get; set; }
}
I defined the UserA and the UserB which are two FK of a User that are contained inside AspNetUsers table.
Now inside the FluentAPI I declared the following:
builder.Entity<UserFriendship>(entity =>
{
entity.HasKey(f => f.Id);
entity.HasOne(u => u.UserA)
.WithMany(n => n.UserFriendships)
.HasForeignKey(u => u.UserAId)
.IsRequired();
entity.HasOne(u => u.UserB)
.WithMany(n => n.UserFriendships)
.HasForeignKey(u => u.UserBId)
.IsRequired();
});
when I execute this command:
add-migration InitialMigration -context MyAppContext
I'll get:
Cannot create a relationship between 'User.UserFriendships' and 'UserFriendship.UserB', because there already is a relationship between 'User.UserFriendships' and 'UserFriendship.UserA'. Navigation properties can only participate in a single relationship.
I'm not an expert of EnityFramework, but based on that error I think that I cannot define two FK in the same table?
Sorry for any mistake, thanks.
You can define more than one FK in table.
The problem here is you are pointing two times to one navigation property - UserFriendships. The solution would be to create two navigation properties.
Navigation properties are used to browse the related data for specified foreign-key (you have one-to-many relationship) of entity.
Try this:
public class User
{
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
public ICollection<UserFriendship> UserAFriendships { get; set; }
public ICollection<UserFriendship> UserBFriendships { get; set; }
}
public class UserFriendship
{
public int Id { get; set; }
public string UserAId { get; set; }
public User UserA { get; set; }
public string UserBId { get; set; }
public User UserB { get; set; }
public DateTime DateTime { get; set; }
}
And define the relationship through fluent api as following:
modelBuilder.Entity<UserFriendship>(entity =>
{
entity.HasKey(f => f.Id);
entity.HasOne(u => u.UserA)
.WithMany(n => n.UserAFriendships)
.HasForeignKey(u => u.UserAId)
.IsRequired();
entity.HasOne(u => u.UserB)
.WithMany(n => n.UserBFriendships)
.HasForeignKey(u => u.UserBId)
.IsRequired();
});
What is more - you don't need to specify attributes Key, ForeignKey if you use Fluent API.

Entity mapping issue

When post gets triggered, it does not save user first and the last name. I have a user class, which inherits from identityuser class and i have declared two properties first and last name as in the code snippet. But problem is when I add the post to the repo it does not save user name rather it shows null value, which throws an exception.
I don't understand how to map, so that user name gets saved in the database.
public abstract class AuditableEntity
{
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
public DateTime PostCreatedDate { get; set; }
[Required]
public User User { get; set; }
}
public class TechPost : AuditableEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
public class User : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class TechPostCreation
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Title { get; set; }
public DateTime PostCreatedDate { get; set; }
public string Description { get; set; }
}
[HttpPost()]
public IActionResult CreatePost([FromBody] TechPostCreation techCreatePost)
{
if (techCreatePost == null)
{
return BadRequest();
}
var techPostEntity = _mapper.Map<TechPost>(techCreatePost);
_repo.AddTechBlogPost(techPostEntity);
if (!_repo.Save())
{
return StatusCode(500, "A problem happend and could handle your request");
}
var techPostReturn = _mapper.Map<TechPostViewModel>(techPostEntity);
return CreatedAtRoute("GetTechPost", new { id = techPostReturn.Id },
techPostReturn);
}
public RepoMapping()
{
CreateMap<TechPost, TechPostViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src =>
$"{src.User.FirstName} {src.User.LastName}"));
CreateMap<TravelPost, TravelPostViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src =>
$"{src.User.FirstName} {src.User.LastName}"));
CreateMap<TechPostCreation, TechPost>();
}
This line:
var techPostEntity = _mapper.Map<TechPost>(techCreatePost);
Uses this map:
CreateMap<TechPostCreation, TechPost>();
Since your User class is a child of TechPost, you need a MapFrom clause. You may instead be able to rename the fields to UserFirstName/UserLastName so the convention kicks in.
Change your map:
CreateMap<TechPostCreation, TechPost>()
.ForMember(dest => dest.User.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.User.LastName, opt => opt.MapFrom(src => src.LastName));
Also, I am not sure what your repo is doing, but you may need to retrieve or create the User with a UserManager at some point in your process.

How can i access related data and convert foreign keys into objects for displaying their properties

I have
class User {
...
...
ICollection<Transaction> transactionsUserMade;
}
and
class Transaction {
int ID;
int userThatSentMoneyID;
int userToWhomHeSentMoneyID;
}
I'm trying to make profile page where user can see all transactions he made and to whom. I managed to relate users and transaction but I'm getting integer values, as i should by using
await _context.Users
.Include(u => u.transactionsUserMade)
.AsNoTracking()
.FirstOrDefaultAsync(u => u.ID == userId);
How can i turn those ID's to actual objects of Users so i could get their usernames and display them on Razor Page.
Found one solution. I tweaked Transaction class by adding User userThatRecievedMoney property. And after getting transactions from specific user i manually set that property.
foreach(var transaction in _user.transactionsUserMade)
{
transaction.userThatRecievedMoney = _context.Users
.Where(u => u.ID == transaction.userToWhomHeSentMoneyID).FirstOrDefault();
}
You can use Navigation Property to help you with that as long as you can modify those entity models User and Transaction.
public class UserEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<TransactionEntity> TransactionsAsSender { get; set; }
public List<TransactionEntity> TransactionsAsRecipient { get; set; }
}
public class TransactionEntity
{
public int Id { get; set; }
public double Amount { get; set; }
public string Note { get; set; }
// Foreign key to UserEntity
public int SenderId { get; set; }
public UserEntity Sender { get; set; }
// Foreign key to UserEntity
public int RecipientId { get; set; }
public UserEntity Recipient { get; set; }
}
Then you need to setup their relationships.
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserEntity>(b => {
b.HasKey(x => x.Id);
b.Property(x => x.Name).IsRequired();
b.Property(x => x.Email).IsRequired();
b.ToTable("User");
});
builder.Entity<TransactionEntity>(b => {
b.HasKey(x => x.Id);
b.Property(x => x.Amount).IsRequired();
// Configure relationships
b.HasOne(x => x.Sender)
.WithMany(u => u.TransactionsAsSender)
.HasForeignKey(x => x.SenderId);
b.HasOne(x => x.Recipient)
.WithMany(u => u.TransactionsAsRecipient)
.HasForeignKey(x => x.RecipientId);
b.ToTable("Transaction");
});
}
public DbSet<UserEntity> Users { get; set; }
public DbSet<TransactionEntity> Transactions { get; set; }
}
After their relationships are setup, you can easily query the related data via navigation properties.
For example, let's say you have view model called UserProfileViewModel and UserProfileTransactionViewModel to contain the information it needs for display purpose.
public class UserProfileViewModel
{
public int UserId { get; set; }
public string UserName { get; set; }
public string UserEmail { get; set; }
public IEnumerable<UserProfileTransactionViewModel> TransactionsAsSender { get; set; }
public IEnumerable<UserProfileTransactionViewModel> TransactionsAsRecipient { get; set }
}
public class UserProfileTransactionViewModel
{
public int TransactionId { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
public string Amount { get; set; }
}
In the controller,
var user = _dbContext.Users
.AsNoTracking()
.Include(x => x.TransactionsAsSender)
.Include(x => x.TransactionsAsRecipient)
.SingleOrDefault(x => x.Id == userId);
if (user == null)
{
return NotFound();
}
var vm = new UserProfileViewModel
{
UserId = user.Id,
UserName = user.Name,
UserEmail = user.Email,
TransactionsAsSender = user.TransactionsAsSender
.Select(x => new UserProfileTransactionViewModel
{
TransactionId = x.Id,
Sender = x.Sender.Name,
Recipient = x.Recipient.Name,
Amount = x.Amount.ToString("c")
}),
TransactionsAsRecipient = user.TransactionsAsRecipient
.Select(x => new UserProfileTransactionViewModel
{
TransactionId = x.Id,
Sender = x.Sender.Name,
Recipient = x.Recipient.Name,
Amount = x.Amount.ToString("c")
})
};
return View(vm);
You could even have just a list of all transactions off UserProfileViewModel. You can combine TransactionsAsSender and TransactionsAsRecipient from UserEntity to fill the list.
Disclaim:
I wrote everything by hand and with my imagination :p

Resources