I am quite new to EF Core 6.0. We currently have a projet to upgrade, we cannot change the actual tables (use by another program) so we use Database fisrt approch.
So I need to add some Permission on user (the database are in french) We curently have an UsagerEW table (user table) and we add an Permission Table and an joint table PermissionUsagerEW for the Many2Many. After doing Scaffold-dbContect here is the result:
UsagerEW (primary key is Code_Int)
public partial class UsagerEW
{
public UsagerEW()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
RefreshToken = new HashSet<RefreshToken>();
}
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
Pemrssion and PermissionUsagerEW
public partial class Permission
{
public Permission()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
}
public int id { get; set; }
public string code { get; set; }
public string description { get; set; }
public int? moduleId { get; set; }
public virtual Module module { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
public partial class PermissionUsagerEW
{
public int id { get; set; }
public int permissionId { get; set; }
public int usagerCodeInt { get; set; }
public virtual Permission permission { get; set; }
public virtual UsagerEW usagerCodeIntNavigation { get; set; }
}
That compile and I can "navigate with include" from UsagerEW and get an list of PermissionUsagerEW for a specific UsagerEW.
Now like I am in EF COre 6.0 that supposed to support Many2Many
I add this nav propertie in the Permnission class
public virtual ICollection<UsagerEW> UsagerEW { get; set; }
and this in the UsagerEW class:
public virtual ICollection<Permission> Permission { get; set; }
But I got execution error either I just try to load some user wintout any include:
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).SingleOrDefault();
System.InvalidOperationException: 'Cannot use table
'PermissionUsagerEW' for entity type 'PermissionUsagerEW
(Dictionary<string, object>)' since it is being used for entity type
'PermissionUsagerEW' and potentially other entity types, but there is
no linking relationship. Add a foreign key to 'PermissionUsagerEW
(Dictionary<string, object>)' on the primary key properties and
pointing to the primary key on another entity type mapped to
'PermissionUsagerEW'.'
The FK are detect by the scaffold:
modelBuilder.Entity<PermissionUsagerEW>(entity =>
{
entity.HasOne(d => d.permission)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.permissionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Permission");
entity.HasOne(d => d.usagerCodeIntNavigation)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.usagerCodeInt)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Usager");
});
Any idea?
---EDIT 1
I change your code to reflect the scaffolded PermissionUsagerEW table:
//--UsagewrEW
modelBuilder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
modelBuilder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.permission)
.WithMany()
.HasForeignKey(e => e.permissionId),
p => p.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(e => e.usagerCodeInt)
);
modelBuilder.Entity<PermissionUsagerEW>()
.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(p => p.usagerCodeInt);
When testing with
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();
Now I got this error:
Microsoft.Data.SqlClient.SqlException: 'Invalid column name
'UsagerEWcode_int'.'
I think EF tries to link something automatically. I do not have any UsagerEWcode_int in my solution.
EDIT2:
There is the SQL generated. Wierd column name and some repetition...
SELECT [u].[code_int], [u].[Administrateur], [u].[Code], [u].[Email], [u].[EmpContact], [u].[Inactif], [u].[KelvinConfig], [u].[LectureSeule], [u].[ModeLogin], [u].[Nom], [u].[ParamRole], [u].[Password], [u].[PasswordTemp], [u].[PasswordTempExp], [u].[RestreintCommContrat], [u].[RestreintProjet], [u].[Role], [u].[UsagerAD], [u].[doitChangerPW], [u].[estSuperviseur], [u].[idSuperviseur], [u].[infoSession], [u].[paramRole2], [u].[permsGrps], [t].[id], [t].[Permissionid], [t].[UsagerEWcode_int], [t].[permissionId0], [t].[usagerCodeInt], [t].[id0], [t].[code], [t].[description], [t].[moduleId]
FROM [UsagerEW] AS [u]
LEFT JOIN (
SELECT [p].[id], [p].[Permissionid], [p].[UsagerEWcode_int], [p].[permissionId] AS [permissionId0], [p].[usagerCodeInt], [p0].[id] AS [id0], [p0].[code], [p0].[description], [p0].[moduleId]
FROM [PermissionUsagerEW] AS [p]
INNER JOIN [Permission] AS [p0] ON [p].[permissionId] = [p0].[id]
) AS [t] ON [u].[code_int] = [t].[usagerCodeInt]
WHERE [u].[Code] = #__usagerId_0
ORDER BY [u].[code_int], [t].[id]
You can configure direct Many-to-Many relationships with an existing database, and you can have the linking entity in the model or exclude it. There are several examples in the docs. And you can leave the foreign key properties in the model, or you can replace them with shadow properties. But the Scaffolding code doesn't do any of this for you. It creates the simplest correct model for the database schema.
Also you usually should rename the entities and properties to align with .NET coding conventions.
Anyway something like this should work:
public partial class UsagerEW
{
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<Permission> Permissions { get; } = new HashSet<Permission>();
}
public partial class Permission
{
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public int? ModuleId { get; set; }
//public virtual Module module { get; set; }
public virtual ICollection<UsagerEW> Users { get; } = new HashSet<UsagerEW>();
}
public partial class PermissionUsagerEW
{
public int Id { get; set; }
public int PermissionId { get; set; }
public int UsagerCodeInt { get; set; }
public virtual Permission Permission { get; set; }
public virtual UsagerEW User { get; set; }
}
public class Db : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
builder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.Permission)
.WithMany()
.HasForeignKey(e => e.PermissionId),
p => p.HasOne(p => p.User)
.WithMany()
.HasForeignKey( e => e.UsagerCodeInt)
);
builder.Entity<PermissionUsagerEW>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UsagerCodeInt);
foreach (var prop in builder.Model.GetEntityTypes().SelectMany(e => e.GetProperties()))
{
prop.SetColumnName(char.ToLower(prop.Name[0]) + prop.Name.Substring(1));
}
base.OnModelCreating(builder);
}
But when you're working in a database-first workflow, there's a downside to deeply customizing the EF model: you loose the ability to regenerate the EF model from the database.
So you can use a "nice" customized EF model, or a "plain" scaffolded model. If you customize the model, you can no longer regenerate it, and need to alter it to match future database changes by hand.
You can apply some customizations, though, like the convention-based property-to-column and entity-to-table mappings in the example. But changing the generated "indirect many-to-many" to "direct many-to-many" will prevent you from regenerating the EF model through scaffolding.
I have an entity that has a many to many onto itself
public class Docket
{
public long Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> RelateDockets{ get; set; }
}
public class DocketDocket
{
public int LeftDocketId { get; set; }
public Docket.Docket LeftDocket { get; set; }
public int RightDocketId { get; set; }
public Docket.Docket RightDocket { get; set; }
}
With the following config
modelBuilder.Entity<Joins.DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.LeftDocket).WithMany(t => t.RelatedDockets).HasForeignKey(pt => pt.LeftDocketId);
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.RightDocket).WithMany().HasForeignKey(pt => pt.RightDocketId).OnDelete(DeleteBehavior.Restrict);
I then manually create the link in my repo as such
await base.Insert(new Joins.DocketDocket() { LeftDocketId = item.Id, RightDocketId = i.RightDocketId });
This works fine but I need this relationship to be double sided so I add the record for the other side
await base.Insert(new Joins.DocketDocket() { LeftDocketId = i.RightDocketId, RightDocketId = item.Id });
and on this second insert I get
Violation of PRIMARY KEY constraint 'PK_RelatedDockets'. Cannot insert duplicate key in object 'dbo.RelatedDockets'. The duplicate key value is (10791, 10790).
Shouldn't EF have my key as (10790, 10791) for the first entry and then (10791,10790) for the second one and therefore NOT duplicate? If not how can I define a unique key for this type of arrengement?
You need to change your config and need to modify Docker entity.
Config:
modelBuilder.Entity<DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<DocketDocket>().HasOne(m => m.LeftDocket).WithMany(t => t.LeftDockets).HasForeignKey(m => m.LeftDocketId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DocketDocket>().HasOne(m => m.RightDocket).WithMany(t => t.RightDockets).HasForeignKey(m => m.RightDocketId).OnDelete(DeleteBehavior.Restrict);
Entity:
public class Docket {
public int Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> LeftDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> RightDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> AllDockets => LeftDockets.Union(RightDockets).ToList();
}
Example: https://dotnetfiddle.net/m6tgDk
I have the following tables : Products, Users and ProductApproval. Whenever user create a new product, the ProductApproval will have a new record with the product ID and a null ApprovedByUserId because it is not approve yet. User can have many ProductApproval but Product only can have one ProductApproval.
The structure is like this:
User.cs
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<ProductApproval> ProductApprovals { get; set; }
public User()
{
ProductApprovals = new Collection<ProductApproval>();
}
}
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ProductApproval ProductApproval { get; set; }
}
ProductApproval.cs
public class ProductApproval
{
public int Id { get; set; }
public virtual Product Product { get; set; }
public int? ProductId { get; set; }
public virtual User User { get ;set; }
public int? ApprovedByUserId { get; set; }
}
DataContext.cs
modelBuilder.Entity<User>()
.HasMany(u => u.ProductApprovals)
.WithOne(pa => pa.User)
.HasForeignKey(pa => pa.ApprovedByUserId)
.IsRequired(false);
modelBuilder.Entity<Product>()
.HasOne(p => p.ProductApproval)
.WithOne(pa => pa.Product)
.HasForeignKey<ProductApproval>(pa => pa.ProductId);
I already declared the foreign key ApprovedByUserId as nullable, but i still get the following error when i insert a record into productapprovals :
MySqlException: Cannot add or update a child row: a foreign key constraint fails (mysite.productapprovals, CONSTRAINT FK_ProductApprovals_Users_ApprovedByUserId FOREIGN KEY (ApprovedByUserId) REFERENCES users (Id) ON DELETE RESTRICT)
Please advice is there any place i did wrong
I have 2 entities configured using Entity Framework 6.
Both entities have Identity on for generating primary keys on insert.
When i try adding new customer I get following error.
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'.
My assumption is that I did not configured one of the entities properly for one to one relationships.
public class Customer : IdEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid? BalanceId { get; set; }
public DateTime? Dob { get; set; }
public DateTime CreateDate { get; set; }
public CustomerBalance Balance { get; set; }
public Address Address { get; set; }
public virtual ICollection<Email> Emails { get; set; } = new List<Email>();
public virtual ICollection<Phone> Phones { get; set; } = new List<Phone>();
public virtual ICollection<Reward> Rewards { get; set; }
public ICollection<Call> Calls { get; set; }
}
Here is mapping for Customer
public class CustomerConfiguration : EntityTypeConfiguration<Customer>
{
public CustomerConfiguration()
{
ToTable(nameof(Customer));
HasKey(x => x.Id).Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasMany(x => x.Phones);
HasMany(x => x.Emails);
HasOptional(x => x.Balance);
HasRequired(x => x.Address).WithRequiredDependent(x=>x.Customer);
}
}
Here is Address Entity
public class Address : IdEntity
{
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
And mapping
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
ToTable(nameof(Address));
HasKey(x => x.Id, e => e.IsClustered())
.Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
Here is a code how i insert new customer
var customer = new Customer
{
FirstName = request.FirstName,
LastName = request.LastName,
CreateDate = DateTime.Now,
Address = new Address()
};
if (!string.IsNullOrEmpty(request.Email))
customer.Emails.Add(new Email { EmailName = request.Email, IsPrimary = true });
if (!string.IsNullOrEmpty(request.Phone))
customer.Phones.Add(new Phone { Number = request.Phone, IsPrimary = true });
_repository.AddOneAsync(customer);
await _repository.Context.SaveChangesAsync();
Error is happening on save changes.
Address and Customer are one to one relationship.
Here is how my tables are configured at SQL
https://ibb.co/cYYphbJ
https://ibb.co/LnXcsyB
One-to-One relationships in EF6 must use the same keys. EG Address.CustomerId would have to be its key.
Either allow customers to have multiple addresses in the model, or change the key of Address to be CustomerID.
Is it possible to create 2 M:M relationships using the same join table?
I have the following situation and am receiving the exception:
Unhandled Exception: System.InvalidOperationException: Cannot create a relationship between 'ApplicationUser.ExpertTags' and 'UserTag.User', because there already is a relationship between 'ApplicationUser.StudyTags' and 'UserTag.User'. Navigation properties can only participate in a single relationship
In Tag:
public class Tag {
public Tag() {
Users = new List<UserTag>();
}
public int TagId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<UserTag> Users { get; set; }
In ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
StudyTags = new HashSet<UserTag>();
ExpertTags = new HashSet<UserTag>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public ICollection<UserTag> StudyTags { get; set; }
public ICollection<UserTag> ExpertTags { get; set; }
}
In UserTag (CLR join):
public class UserTag
{
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
In ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserTag>()
.HasKey(x => new { x.UserId, x.TagId });
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.StudyTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.ExpertTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.Tag)
.WithMany(t => t.Users)
.HasForeignKey(ut => ut.TagId);
}
Do I need to create separate CLR classes? Something like UserStudyTag and UserExpertTag?
Thanks!
Step down to SQL DB. You want to have table UserTag with one UserId field. How EF should guess, which records in this table are related to StudyTags and which to ExpertTags collections?
You should duplicate something.
Either split UserTag to two tables (UserStudyTag and UserExpertTag), or make two UserId fields in UserTag, say ExpertUserId and StudyUserId. Both nullable, with only one having some value in each record.