I have three tables:
UserProfiles (UserId is the primary key
UserId Email
------ ------------
9032 bill#any.com
WebpagesRoles (RoleId is the primary key)
RoleId RoleName
------ ------------------
0 Site User
56 Admin
(Note the zero RoleId - that's the tricky bit.)
And WebpagesUsersInRoles, where both columns are foreign keys to the other tables.
I want to add these rows to WebpagesUsersInRoles
UserId RoleId
------ ------
9032 0
9032 56
My C#, EFCore 3.1 application has been deoptimised to aid investigation
foreach (var uirToAdd in uirsToAdd)
{
_context.WebpagesUsersInRoles.Add(uirToAdd);
// var role = _context.WebpagesRoles.FirstOrDefault(r => r.RoleId == uirToAdd.RoleId);
// uirToAdd.Role = role;
await _context.SaveChangesAsync(CancellationToken.None);
}
The code snippet above can write the "Admin" row where RoleId is non-zero, but it can't write the "SiteUser" row where RoleId is zero. It fails on the SaveChangesAsync with the message 'The INSERT statement conflicted with the FOREIGN KEY constraint "fk_RoleId"'.
However, if I uncomment those two lines, it works fine.
Why?
Note, we are not using WebSecurity, just the tables, and so a command like roles.AddUserToRole("admin1", "Admin") is not appropriate. In any case, I'm just as curious about what's causing the problem as in finding a solution.
Edit
For reference, here's the actual model, including some parts I left out of the question for brevity:
public partial class WebpagesRole
{
public WebpagesRole()
{
ActionDropdownRoles = new HashSet<ActionDropdownRole>();
ActionTypeRoles = new HashSet<ActionTypeRole>();
ClientTypeRoles = new HashSet<ClientTypeRole>();
ScimUserGroupRoles = new HashSet<ScimUserGroupRole>();
WebpagesUsersInRoles = new HashSet<WebpagesUsersInRole>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public string Description { get; set; }
public virtual ICollection<ActionDropdownRole> ActionDropdownRoles { get; set; }
public virtual ICollection<ActionTypeRole> ActionTypeRoles { get; set; }
public virtual ICollection<ClientTypeRole> ClientTypeRoles { get; set; }
public virtual ICollection<ScimUserGroupRole> ScimUserGroupRoles { get; set; }
public virtual ICollection<WebpagesUsersInRole> WebpagesUsersInRoles { get; set; }
}
Related
I created a linked table between Users and Tenants called UserTenants.
Now I want to drop the foreign key column: UserId. The problem is that I can't find the model for UserClaim which I can edit. I've tried to create a new one like this:
public class UserClaim : CreationAuditedEntity<long>{
[Key]
public int Id { get; set; }
public const int MaxClaimTypeLength = 256;
public virtual int? TenantId { get; set; }
public virtual long UserId { get; set; }
public virtual User User { get; set; }
[StringLength(256)]
public virtual string ClaimType { get; set; }
public virtual string ClaimValue { get; set; }
public UserClaim() {
}
public UserClaim(AbpUserBase user, Claim claim)
{
TenantId = user.TenantId;
UserId = user.Id;
ClaimType = claim.Type;
ClaimValue = claim.Value;
}
}
But the Error I'm getting is:
System.InvalidOperationException: Cannot use table 'UserClaims' for entity type 'UserClaim' since it is being used for entity type 'UserClaim' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'UserClaim' on the primary key properties and pointing to the primary key on another entity type mapped to 'UserClaims'.
Let's say I have a data model that looks like so:
dbo.Application
{
int Id { get; set; }
int ClientAlternateId { get; set; }
...
public virtual Client Client { get; set; }
}
dbo.Client
{
int Id { get; set; }
int AlternateId { get; set; }
string Name { get; set; }
...
}
Where the primary key on both tables is the Id column. the Application type is associated to the Client table via its AlternateId column (which is not a key). The data in this column is always unique.
Is there a way to get entity framework to map this? I don't believe I'll be able to use:
HasRequired(t => t.Client).WithMany().HasForeignKey(c => c.ClientAlternateId);
since that field is not the primary key.
When configuring the relationship in your AppDbContext you need to add .HasPrincipalKey()
, this article might help
https://gavilan.blog/2019/04/14/entity-framework-core-foreign-key-linked-with-a-non-primary-key/
I have the following model set up:
public class User
{
[Key]
[Required]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string UserName { get; set; }
[Required]
public TypeOfProfile ProfileType { get; set; }
[StringLength(50)]
public string ProfileName { get; set; }
I don't want to use TPT. I want the user table to be the short summary table for quick loading. Once clicking on a User his profile is fetched. I have multiple types of profiles. E.g
public class Person | Business | Artist
{
[Key]
[Required]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
public int AddressId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
public virtual Address Address { get; set; }
public virtual IList<Deal> Deals { get; set; }
public virtual IList<Event> Events { get; set; }
public virtual IList<Vacancy> Vacancies { get; set; }
This way I can create a profile for a certain user and give it the same Id as the User Id, hence the DatabaseGeneratedOption.None. This all works fine. But then the collections come into place: Deals, Events, Vacancies,..
public class Event
{
[Key]
[Required]
[Column(Order = 0)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Column(Order = 1)]
public int UserId { get; set; }
[Required]
public int AddressId { get; set; }
...
public virtual User User { get; set; }
public virtual Address Address { get; set; }
All is created just fine with Code First except for one thing. In the Events, Deals and Vacancies table a foreign key is created for each type on top of the UserId property of Event, Deal and Vacancy.
Due to this, the model insists on having a profile of each type for the User, while I only want 1 type of profile per user:
Column:
Id (PK, int, not null)
UserId (FK, int, not null)
Keys:
PK_dbo.Events
FK_dbo.Events.dbo.Artists_UserId
FK_dbo.Events.dbo.Businesses_UserId
FK_dbo.Events.dbo.People_UserId
FK_dbo.Events.dbo.Users_UserId
...
I only want the foreign key to the Users_Id to be created. When I delete the other fk's to people, businesses and artists, then everything is working as i want it. Like this:
Column:
Id (PK, int, not null)
UserId (FK, int, not null)
Keys:
PK_dbo.Events
FK_dbo.Events.dbo.Users_UserId
...
But how can I configure code first or by fluent API that the 3 extra foreign keys are not created. (due to the ILists of those Profiles) I don't want to be able to retrieve a profile from the event object, only the User object.
Thanks in advance!
Kr
I solved my problem using a TPH architecture. (Table Per Hierarchy) Now I have only one Profile table with a discriminator for Person, Business and Artist. The primary key of my Profile table is at the same time the foreign key to my User table.
Kr
I have a problem with a navigation property not being loaded. I have this same setup with all my other entities, but this is using a property that isnt a natural FK (Number) and wont cascade, that will be handled by a trigger.
Expression<Func<DivisionBracketGameParticipant, object>>[] includes2 = {
q => q.DivisionWinnerBracketGame,
q => q.DivisionLoserBracketGame
};
var test = _divisionBracketGameParticipantsRepository.GetMany(includes2,
q =>
q.DivisionBracketGame.DivisionBracket.Division.
EventId == eventId);
Database Schema
DivisionBracketGame
Id
Number
DivisionBracketGameParticipant
Id
DivisionBracketGameId -> Id
DivisionBracketGameWinnerNumber -> Number
DivisionBracketGameLoserNumber -> Number
Entities
[Table("DivisionBracketGame", Schema = "GrassrootsHoops")]
public class DivisionBracketGame : BaseEntity
{
public int Id{ get; set; }
public int Number { get; set; }
[InverseProperty("DivisionBracketGame")]
public virtual ICollection<DivisionBracketGameParticipant> DivisionBracketGameParticipants { get; set; }
[InverseProperty("DivisionWinnerBracketGame")]
public virtual ICollection<DivisionBracketGameParticipant> DivisionWinnerBracketGameParticipants { get; set; }
[InverseProperty("DivisionLoserBracketGame")]
public virtual ICollection<DivisionBracketGameParticipant> DivisionLoserBracketGameParticipants { get; set; }
}
[Table("DivisionBracketGameParticipant", Schema = "GrassrootsHoops")]
public class DivisionBracketGameParticipant : BaseEntity
{
public int Id{ get; set; }
public virtual int DivisionBracketGameId { get; set; }
public virtual int? DivisionWinnerBracketGameNumber { get; set; }
public virtual int? DivisionLoserBracketGameNumber { get; set; }
[ForeignKey("DivisionBracketGameId")]
public virtual DivisionBracketGame DivisionBracketGame { get; set; }
[ForeignKey("DivisionWinnerBracketGameNumber")]
public virtual DivisionBracketGame DivisionWinnerBracketGame { get; set; }
[ForeignKey("DivisionLoserBracketGameNumber")]
public virtual DivisionBracketGame DivisionLoserBracketGame { get; set; }
}
EF will not create relation to Number because it is not a primary key. Primary key is of your DivisionBracketGame is Id so both DivisionWinnerBracketGame and DivisionLoserBracketGame are targeting Id (not Number).
One-to-many relation demands that column in principal table is unique - in your case the column should be a Number. This is possible in database by either using primary key from principal table or by using unique index on that column. EF doesn't support unique indexes / candidate keys so the only way to build one-to-many relation in EF is through primary key of principal table.
The FK value is used to get related value so at the moment it probably looks for records with wrong value because it uses a wrong column.
I'm using Entity Framework 4.1 and trying to create a one-to-many relationship. In my software I have UserEntity and RoleEntity classes. Each User has one Role but one Role can have many Users.
So far I've succeeded in getting the data from the DB to the UserEntity except for the related RoleEntity object. The RoleId property of my test user object has the correct RoleID but Role property is null.
Is there some kind of additional configuration that I would need to write before the Role property is populated with the correct RoleEntity?
[Table("Roles")]
public class RoleEntity
{
public long Id { get; set; }
public string Name { get; set; }
}
[Table("Users")]
public class UserEntity
{
public long Id { get; set; }
public string Password { get; set; }
public Int32 IsActive { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string LocalService { get; set; }
[ForeignKey("RoleEntity")]
public long RoleId { get; set; }
public virtual RoleEntity Role { get; set; }
public virtual ClientOrganizationEntity ClientOrganization { get; set; }
}
public class UserContext : DbContext
{
#region Constructor
public UserContext(DbConfigurationProvider dbConfigurationProvider)
: base(dbConfigurationProvider.ConnectionString)
{
if (dbConfigurationProvider == null)
throw new ArgumentNullException("dbConfigurationProvider");
Configuration.ProxyCreationEnabled = false;
}
#endregion
#region DbSets
public DbSet<UserEntity> Users { get; set; }
public DbSet<RoleEntity> Roles { get; set; }
public DbSet<ClientOrganizationEntity> ClientOrganizations { get; set; }
#endregion
}
Data in Users table:
Id User Password FirstName LastName Email LocalService IsActive RoleId ClientOrganizationId
1 foo 62cdb7020ff920e5aa642c3d4066950dd1f01f4d FirstName LastName te#s.t foobar.com 1 1 1
Data in Roles table:
Id Name
1 Administrator
EDIT:
This how I get the data from the DbContext:
var user = _dbContext.Users.FirstOrDefault(x => x.Id == 1);
var role = user.Role;
I didn't see that you have disabled proxy creation in the constructor by
Configuration.ProxyCreationEnabled = false
Lazy loading will not work when you disable Proxy creation.
So either Enable proxy creation or use Include method to eager load the navigational properties
var user = _dbContext.Users.Include(u => u.Role).FirstOrDefault(x => x.Id == 1);
You need to enable lazy loading Configuration.LazyLoadingEnabled=true; in your UserContext constructor.
I ended up solving this myself. For some disabling of Configuration.ProxyCreationEnabled broke the relationships. The only change I made to fix it was remove this line:
Configuration.ProxyCreationEnabled = false;
Surely relationships must be possible to be used even with proxy creation disabled so if anyone has any additional information, I'd like to hear it. For the time being however, I'm just glad that the problem was solved.