EF Core Join using Include but ForeignKey is not Primary Key on the other table - asp.net-core

I am trying to relate my Tables with ForeignKey and PrimaryKey on the other end. But now i will be using a ForeignKey which is not the primary for the said table. I was using [InverseProperty] but i think there's a bug with it since i've been looking around for hours already and all of them says the same thing about it.
Documents Table:
public class Document
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DocumentId { get; set; }
public int ProjectId { get; set; }
public int DepartmentId { get; set; }
public int AuthorId { get; set; }
[NotMapped]
public virtual User Author { get; set; }
}
Users
public class User
{
[Key]
public int UserId { get; set; }
public int AuthUserId { get; set; }
public string DisplayName { get; set; }
[NotMapped]
[ForeignKey("AuthorId")]
public virtual Document Document { get; set; }
}
Context:
modelBuilder.Entity<User>(entity =>
{
entity.HasOne(u => u.Document)
.WithMany("AuthorId");
});
I am trying to use the solution they here, but no luck.
Any help would really be appreciated. Thanks!

But now i will be using a ForeignKey which is not the primary for the said table.
To do this you can use EF Core Alternate Keys feature. But first correct your model class set up as follows: (As you said a User will have multiple Document)
public class Document
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DocumentId { get; set; }
public int ProjectId { get; set; }
public int DepartmentId { get; set; }
public int AuthorId { get; set; }
public User Author { get; set; }
}
public class User
{
[Key]
public int UserId { get; set; }
public int AuthUserId { get; set; }
public string DisplayName { get; set; }
public ICollection<Document> Documents { get; set; }
}
Then in the Fluent API configuration as follows:
modelBuilder.Entity<Document>()
.HasOne(p => p.Author)
.WithMany(b => b.Documents)
.HasForeignKey(p => p.AuthorId)
.HasPrincipalKey(b => b.AuthUserId); // <-- here you are specifying `AuthUserId` as `PrincipalKey` in the relation which is not primary key

Related

Is it possible to link one table to another with entity framework core without FKs?

I have 2 tables company and user. Company will have one created by user and one modified user - these will be admin users. User will belong to one company but one admin user could create or modify multiple companies.
I'm having a hard time using entity framework core in my .net core app to join company and user so when I get a company record I have the created by and modified user information.
My company and user classes look like this:
public class Company
{
[Key]
public Guid Id { get; set; }
public DateTime Created { get; set; }
public Guid Created_By { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
public DateTime Modified { get; set; }
public Guid Modified_By { get; set; }
public virtual ApplicationUser ModifiedByUser { get; set; }
public string Company_Name { get; set; }
}
public class ApplicationUser: IdentityUser<Guid>
{
[Column("ID")]
public override Guid Id { get; set; }
[Column("CREATED")]
public DateTime Created { get; set; }
[Column("CREATED_BY")]
public Guid? CreatedBy { get; set; }
[Column("MODIFIED")]
public DateTime Modified { get; set; }
[Column("MODIFIED_BY")]
public Guid? ModifiedBy { get; set; }
[Column("FIRST_NAME")]
public string FirstName { get; set; }
[Column("LAST_NAME")]
public string LastName { get; set; }
[Column("EMAIL")]
public override string Email { get; set; }
[Column("NORMALIZED_EMAIL")]
public override string NormalizedEmail { get; set; }
[Column("EMAIL_CONFIRMED")]
public override bool EmailConfirmed { get; set; }
[Column("USER_NAME")]
public override string UserName { get; set; }
[Column("NORMALIZED_USER_NAME")]
public override string NormalizedUserName { get; set; }
[Column("COMPANY_ID")]
public Guid CompanyId { get; set; }
[Column("PHONE_NUMBER")]
public override string PhoneNumber { get; set; }
[Column("PHONE_NUMBER_CONFIRMED")]
public override bool PhoneNumberConfirmed { get; set; }
[Column("TITLE")]
public string Title { get; set; }
[Column("ACTIVE")]
public bool Active { get; set; }
[Column("ROLE_ID")]
public int UserRoleId { get; set; }
[Column("TYPE_ID")]
public int TypeId { get; set; }
[Column("PASSWORD_HASH")]
public override string PasswordHash { get; set; }
[Column("SECURITY_STAMP")]
public override string SecurityStamp { get; set; }
[Column("CONCURRENCY_STAMP")]
public override string ConcurrencyStamp { get; set; }
[Column("TWO_FACTOR_ENABLED")]
public override bool TwoFactorEnabled { get; set; }
[Column("LOCKOUT_END")]
public override DateTimeOffset? LockoutEnd { get; set; }
[Column("LOCKOUT_ENABLED")]
public override bool LockoutEnabled { get; set; }
[Column("ACCESS_FAILED_COUNT")]
public override int AccessFailedCount { get; set; }
}
My DbContext class looks like this:
public class DbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<Guid>, IdentityRoleClaim<Guid>, IdentityUserToken<Guid>>
{
public DbContext(DbContextOptions<DbContext> options) : base(options)
{
}
public virtual DbSet<Company> Companies { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>().ToTable("COMPANY").Property<Guid>("Created_By");
builder.Entity<Company>().HasOne(x => x.CreatedByUser).WithOne().HasForeignKey("Created_By");
builder.Entity<ApplicationUser>().ToTable("USER");
builder.Entity<ApplicationUser>().HasKey(x => x.Id);
builder.Entity<ApplicationUserClaim>().ToTable("USER_CLAIMS");
builder.Entity<ApplicationRole>().ToTable("IDENTITY_ROLES");
builder.Entity<IdentityUserRole<Guid>>().HasKey(p => new { p.UserId, p.RoleId });
builder.Entity<ApplicationUserRole>().ToTable("IDENTITY_USER_ROLES");
}
}
And I was trying to get companies like this:
public async Task<List<Company>> GetAllCompanies()
{
return await _locationDbContext.Companies.ToListAsync();
}
Currently I am getting this error:
System.InvalidOperationException: 'You are configuring a relationship between 'Company' and 'ApplicationUser' but have specified a foreign key on 'Created_By'. The foreign key must be defined on a type that is part of the relationship.'
Is there an easier way to do this? Really all I want is the username of the user that created of modified the company record? If I was doing this with just sql I would just use a basic Join but Im not sure how to do that with entity framework. Worst case I would just get all the companies and then loop through doing a select on the user table where ID = Created_By
As #IvanStoev noticed without FKs it is not possible. But you can still use EF to join 2 tables.
In your case you have to unmap user from company and maybe it is a good idea to make user Guid nullable:
public class Company
{
[Key]
public Guid Id { get; set; }
public string Company_Name { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public Guid Created_By { get; set; }
public Guid Modified_By { get; set; }
[NotMapped]
public ApplicationUser CreatedByUser { get; set; }
// or better
[NotMapped]
public string CreatedByUser { get; set; }
[NotMapped]
public ApplicationUser ModifiedByUser { get; set; }
//or better
[NotMapped]
public string ModifiedByUser { get; set; }
}
and remove
builder.Entity<Company>().HasOne(x => x.CreatedByUser).WithOne().HasForeignKey("Created_By");
you still can join them like this
var companies= (
from c in _locationDbContext.Companies
join uc in _locationDbContext.ApplicatonUser on c.Created_By equals uc.Id
join um in _locationDbContext.ApplicatonUser on c.Modified_By equals um.Id
select new Company
{
....
CreatedByUser = uc,
ModifiedByUser = um
// or usually
CreatedByUser = uc.FirstName + " " + uc.LastName,
ModifiedByUser = um.FirstName + " " + um.LastName,
}).ToList();
This is one way of accomplishing this.
This is a simplified version of class Company:
public class Company
{
public int CompanyID { get; set; }
public string Name { get; set; }
//
// Relations
public string CreatorID { get; set; }
public ApplicationUser Creator { get; set; }
public string LastModifiedByID { get; set; }
public ApplicationUser LastModifiedBy { get; set; }
}
CreatorID and LastModifiedByID will be used by EF for determining the FK's. These are the fields you have to work with when updating the Db, not Creator and LastModfiedBy, yet you can also do it but with more lines of code.
Now, you can add companies the usual way, this code searches for the company, and creates one if not found, just sample code, not serious, really:
var companyName = "My company";
var company = context.Companies.Include(c => c.Creator).Include(c => c.Creator).FirstOrDefault(c => c.Name == companyName);
if (company == null)
{
company = new Company
{
Name = "My company",
CreatorID = user.Id,
LastModifiedByID = user.Id
};
context.Companies.Add(company);
context.SaveChanges();
}
And retrieving the companies with all relations filled is a matter or using calls to Include() and ThenInclude(), like here:
var companies = context.Companies.Include(c => c.Creator).Include(c => c.Creator).ToList();
I omitted all filtering logic for the sake of simplicity.

Object reference not set to an instance of an object in .net core

I have two model class as;
public class MessageDetailModel
{
[Key]
public int messageDetailsId { get; set; }
public MessageModel messageModel { get; set; }
public string detail { get; set; }
public int senderId { get; set; }
public int customerId { get; set; }
public string phone { get; set; }
public DateTime date { get; set; }
}
and
public class MessageModel
{
[Key]
public int messageId { get; set; }
public int senderId { get; set; }
public int customId { get; set; }
public bool ReadInfo { get; set; }
public virtual List<MessageDetailModel> MessageDetails { get; set; }
}
and it is my context class ;
public virtual DbSet<MessageDetailModel> messageDetails { get; set; }
public virtual DbSet<MessageModel> messages { get; set; }
public virtual DbSet<PersonModel> persons { get; set; }
public virtual DbSet<DirectoryModel> directory { get; set; }
I am trying to get messageId over MessageDetailModel but messageId returns as 0 and I have that error "Object reference not set to an instance of an object"
Console.WriteLine(k.messageModel.messageId); //( k is my var which gets from messagedetail model)
How can ı reach messageId over MessadeDetailModel
There is no current link between your two models that would represent a Foreign Key.
You'd need to do something like this for your model if you want a Foreign Key to link to the related object:
public class MessageDetailModel
{
[Key]
public int messageDetailsId { get; set; }
[ForeignKey("messageId")] // Added Data Annotation for the Foreign Key relationship
public MessageModel messageModel { get; set; }
public string detail { get; set; }
public int senderId { get; set; }
public int customerId { get; set; }
public string phone { get; set; }
public DateTime date { get; set; }
public int messageId { get; set; } // This would be your Foreign Key
}
I've added the messageId column to your MessageDetailModel to match the Primary Key column of your MessageModel as that's necessary for the link to form.
You would use the [ForeignKey("messageId")] Data Annotation above the variable for the MessageModel to determine what value it needs to use when finding the object you want.

EntityFramework Trying to create multiple links to the same table, FK Constraint error

I have a table called DeliveryRequest and another table called Operator, table DeliveryRequest is as follows:
public class DeliveryRequest
{
public int ID { get; set; }
public DateTime Date { get; set; }
public string UserID { get; set; }
public string Waybill { get; set; }
public string Reference { get; set; }
public int SupplierID { get; set; }
public Supplier Supplier { get; set; }
//[ForeignKey("Operator")]
public int SenderID { get; set; }
public Operator Sender { get; set; }
//[ForeignKey("Operator")]
public int ReceiverID { get; set; }
public Operator Receiver { get; set; }
public string Origin { get; set; }
public string Destination { get; set; }
public int ServiceID { get; set; }
public Service Service { get; set; }
}
And table Operator is as follows:
public class Operator
{
public int ID { get; set; }
public string Company { get; set; }
public int ContactID { get; set; }
public Contact Contact { get; set; }
public int AddressID { get; set; }
public Address Address { get; set; }
}
So the problem is, when I am trying to update my database I get a FK Constraint error as follows:
Introducing FOREIGN KEY constraint
'FK_dbo.DeliveryRequests_dbo.Operators_SenderID' on table
'DeliveryRequests' 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.
And the previous error is the same. As follows:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN
KEY constraint 'FK_dbo.DeliveryRequests_dbo.Operators_SenderID' on
table 'DeliveryRequests' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints.
Focus on the Sender and Receiver part, I am no expert but the error must be there lol
//[ForeignKey("Operator")]
public int SenderID { get; set; }
public Operator Sender { get; set; }
//[ForeignKey("Operator")]
public int ReceiverID { get; set; }
public Operator Receiver { get; set; }
It looks like you are using Code First approach. So try to turn off CascadeDelete for DeliveryRequests:
modelBuilder.Entity<DeliveryRequests>()
.HasRequired(c => c.Operator )
.WithMany()
.WillCascadeOnDelete(false);
For example:
public class YourDBContext: DbContext
{
public YourDBContext(): base()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DeliveryRequests>()
.HasRequired(c => c.Operator )
.WithMany()
.WillCascadeOnDelete(false);
}
}

Is it possible to create a Domain Class which has Multiple FK Columns to same PK?

I'm a newbie to designing database.
I have problem how to define a domain class which has multiple foreign keys linked with a same primary key.
Here is my model:
namespace OceanFmsSystem.Domain
{
public class ExportTemplate
{
public int Id { get; set; }
public List<ExportBooking> ExportBookings { get; set; }
public string TemplateName { get; set; }
public int CustomerId { get; set; }
public string Incoterms { get; set; }
public string IncotermsDetail { get; set; }
public string PaymentTerm{ get; set; }
public int CountryOriginId { get; set; }
public int CountryDestinationId { get; set; }
}
}
What I want to do is that CountryOriginId & CountryDestinationId should refer to the below class as foreign keys:
namespace OceanFmsSystem.Domain
{
public class Country
{
public int Id { get; set; }
public string CountryCode { get; set; }
public string CountryName { get; set; }
}
}
As far as I know, in EF Core there is an convention which I should name a foreign key as below for migration from code to database.
public type ClassNameOfPrimaryKeyId { get; set;}
Is there any possible way to make this happens?
Yes, possible. Your class should look like this:
public class ExportTemplate
{
//...
public int CountryOriginId { get; set; }
public Country CountryOrigin { get; set; }
public int CountryDestinationId { get; set; }
public Country CountryDestination { get; set; }
}
EF is smart enough to figure the Ids by convention. If you do not wish to follow the convention you can use [ForeignKey] attribute on the properties to configure the FK:
[ForeignKey("Origin")]
public int MyOriginId { get; set; }
public Country Origin { get; set; }

Can't see relationship in database diagram entity framework core

I am working on ASP.NET Core application which uses Entity framework core. I am using code first approach to create database model. I am trying the get one-to-many relationship (one user can have multiple products) between following two classes but in database diagram, I can not see that relationship.
public class SystemUser : IdentityUser
{
public SystemUser()
{
this.ProductToUser = new HashSet<ProductsToUser>();
}
[StringLength(200)]
[Required]
public string FullName { get; set; }
[Required]
[StringLength(200)]
public string Address { get; set; }
[Required]
public int PinNo { get; set; }
[Required]
public int StateId { get; set; }
[Required]
public int CountryId { get; set; }
[Required]
public DateTime RegisterDate { get; set; }
public virtual ICollection<ProductsToUser> ProductToUser { get; set; }
}
public class ProductsToUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int ProductID { get; set; }
[ForeignKey("UserId")]
public SystemUser SystemUser { get; set; }
}
Below is the screenshot of database diagram.
As you can see in diagram it is not showing relationship. But I am getting foreign key constraint in ProductsToUser table as shown below
How do I resolve this issue?