I what have many-to-many relationship between two entities. Everything works fine. Is there a way to define names of FKs in intermediate table(StoresPushNotifications)?
The reason to ask is that mysql do not allow to define long constraint names. It generates random FK in case. It breaks migration when I try to set migration to an earlier step.
[Table("Stores")]
public class StoreEntity
{
[Key]
public int Id { get; set; }
public virtual ICollection<PushNotificationEntity> PushNotifications { get; set; }
}
[Table("PushNotifications")]
public class PushNotificationEntity
{
[Key]
public int Id { get; set; }
public ICollection<StoreEntity> Stores { get; set; }
}
In my Context file,
modelBuilder.Entity<StoreEntity>()
.HasMany<PushNotificationEntity>(s => s.PushNotifications)
.WithMany(c => c.Stores)
.Map(cs =>
{
cs.MapLeftKey("StoreId");
cs.MapRightKey("PushNotificationId");
cs.ToTable("StoresPushNotifications");
});
I had a similar problem and it was actually related to the migration key lengthrather than the foreign keys.
I resolved by modifying the migration configuration method to:
internal sealed class Configuration : DbMigrationsConfiguration<CCDatabase.CCDbContext>
public Configuration()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = #"Migrations";
SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
SetHistoryContextFactory("MySql.Data.MySqlClient", (conn, schema) => new MySqlHistoryContext(conn, schema));
}
and then adding this method to limit the key lengths
public class MySqlHistoryContext : HistoryContext
{
public MySqlHistoryContext(DbConnection connection, string defaultSchema) : base(connection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<HistoryRow>().Property(h => h.MigrationId).HasMaxLength(100).IsRequired();
modelBuilder.Entity<HistoryRow>().Property(h => h.ContextKey).HasMaxLength(200).IsRequired();
}
}
Related
I have the traditional ApplicationUser (IdentityUser), and that user can send a friend request to another ApplicationUser. I currently have the following general entity classes:
public class ApplicationUser : IdentityUser
{
public virtual List<DeviceToken> DeviceTokens { get; set; } = new List<DeviceToken>();
public string DisplayName { get; set; }
}
public class FriendRequest
{
public int Id { get; set; }
public DateTime DateRequested { get; set; }
public ApplicationUser Requester { get; set; }
public ApplicationUser Receiver { get; set; }
}
I have ran database-update etc and this is working fine. However when I go into my SQLServer to try to delete an ApplicationUser, it tells me that The DELETE statement conflicted with the REFERENCE constraint "FK_FriendRequest_AspNetUsers_RequesterId".
So I have decided to implement a cascade delete flow from the ApplicationUser to the friend requests that they are part of.
I have tried the resource on here by Microsoft on configuring cascade delete but I cannot figure out how to apply it to my case:
builder.Entity<ApplicationUser>()
.HasMany(e => e.FriendRequests)//No such property, no idea how to address
.OnDelete(DeleteBehavior.ClientCascade);
How do I set up this cascade delete scenario?
Also how do I add a property to ApplicationUser that refers to all the FriendRequests they are part of, and make sure EFCore knows I am referring to that existing FriendRequest entity/table?
Update
Following the suggested approach of adding a virtual property to ApplicationUser, would this be way forward:
public class ApplicationUser : IdentityUser
{
public virtual List<DeviceToken> DeviceTokens { get; set; } = new List<DeviceToken>();
public string DisplayName { get; set; }
public ICollection<FriendRequest> FriendRequests { get; }
}
builder.Entity<ApplicationUser>()
.HasMany(u => u.FriendRequests)
.WithOne(u => u.Requester)
.OnDelete(DeleteBehavior.ClientCascade); //not sure about this
builder.Entity<ApplicationUser>()
.HasMany(u => u.FriendRequests)
.WithOne(u => u.Requester)
.OnDelete(DeleteBehavior.ClientCascade); //not sure about this
Your ApplicationUser needs 2 virtual ICollections.
public class ApplicationUser
{
public int Id { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<FriendRequest> FriendRequestsAsRequestor { get; set; }
public virtual ICollection<FriendRequest> FriendRequestsAsReceiver { get; set; }
}
public class FriendRequest
{
public int Id { get; set; }
public DateTime DateRequested { get; set; }
public int RequestorId { get; set; }
public ApplicationUser Requestor { get; set; }
public int ReceiverId { get; set; }
public ApplicationUser Receiver { get; set; }
}
public class ApplicationUserConfig : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
builder.HasMany(au => au.FriendRequestsAsRequestor)
.WithOne(fr => fr.Requestor)
.HasForeignKey(fr => fr.RequestorId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(au => au.FriendRequestsAsReceiver)
.WithOne(fr => fr.Receiver)
.HasForeignKey(fr => fr.ReceiverId)
.OnDelete(DeleteBehavior.Cascade);
}
}
Use:
void AddFriendRequest(int requestorId, int receiverId)
{
var ctxt = new DbContext();
FriendRequest fr = new FriendRequest
{
RequestorId = requestorId;
ReceiverId = receiverId;
DateRequested = DateTime.Now;
}
ctxt.FriendRequests.Add(fr);
ctxt.SaveChanges();
}
List<FriendRequest> GetFriendRequests()
{
var ctxt = new DbContext();
return ctxt.FriendRequests
.Include(fr => fr.Requestor)
.Include(fr => fr.Receiver)
.ToList();
}
ApplicationUser GetUserWithFriendRequests(int id)
{
var ctxt = new DbContext();
return ctxt.ApplicationUser
.Include(au => au.FriendRequestsAsRequestor)
.Include(au => au.FriendRequestsAsReceiver)
.SingleOrDefault(au => au.Id == id);
}
I have tried the resource on here by Microsoft on configuring cascade delete but I cannot figure out how to apply it to my case:
builder.Entity<ApplicationUser>()
.HasMany(e => e.FriendRequests)//No such property, no idea how to address
.OnDelete(DeleteBehavior.ClientCascade);
From the doc of DeleteBehavior :
ClientCascade : For entities being tracked by the DbContext, dependent entities will be deleted when the related principal is deleted. If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.
In this case, it's the client (the .NET app) and not the DB that ensure the cascade delete. If the client fail to do the cascade delete (related entity not tracked), the db will generate the error you see.
Maybe the DeleteBehavior.Cascade is more appropriate to your code first scenario :
Cascade : For entities being tracked by the DbContext, dependent entities will be deleted when the related principal is deleted. If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is the same as is described above for tracked entities. Keep in mind that some databases cannot easily support this behavior, especially if there are cycles in relationships, in which case it may be better to use ClientCascade which will allow EF to perform cascade deletes on loaded entities even if the database does not support this. This is the default for required relationships. That is, for relationships that have non-nullable foreign keys.
If you try this, you go with this SQL script migration (I assume the SGBDR is SQL Server) :
CREATE TABLE [ApplicationUser] (
[Id] int NOT NULL IDENTITY,
[DisplayName] nvarchar(max) NULL,
CONSTRAINT [PK_ApplicationUser] PRIMARY KEY ([Id])
);
GO
CREATE TABLE [FriendRequests] (
[Id] int NOT NULL IDENTITY,
[DateRequested] datetime2 NOT NULL,
[RequesterId] int NULL,
[ReceiverId] int NULL,
CONSTRAINT [PK_FriendRequests] PRIMARY KEY ([Id]),
CONSTRAINT [FK_FriendRequests_ApplicationUser_ReceiverId] FOREIGN KEY ([ReceiverId]) REFERENCES [ApplicationUser] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_FriendRequests_ApplicationUser_RequesterId] FOREIGN KEY ([RequesterId]) REFERENCES [ApplicationUser] ([Id]) ON DELETE CASCADE
);
GO
And when it's apply, this produce this error :
Introducing FOREIGN KEY constraint 'FK_FriendRequests_ApplicationUser_RequesterId' on table 'FriendRequests' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
First time I see this error, then I will refer to this question with #onedaywhen's answer :
SQL Server does simple counting of cascade paths and, rather than trying to work out whether any cycles actually exist, it assumes the worst and refuses to create the referential actions (CASCADE)...
A no perfect solution is to use DeleteBehavior.Cascade and ensure all related entities are tracked before the delete :
public class ApplicationUser
{
public int Id { get; set; }
public string DisplayName { get; set; }
public ICollection<FriendRequest> RequestedRequests { get; set; }
public ICollection<FriendRequest> RecevedRequests { get; set; }
}
public class FriendRequest
{
public int Id { get; set; }
public DateTime DateRequested { get; set; }
public ApplicationUser Requester { get; set; }
public ApplicationUser Receiver { get; set; }
}
public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer("***");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<FriendRequest>()
.HasOne(r => r.Requester)
.WithMany(u => u.RequestedRequests)
.OnDelete(DeleteBehavior.ClientCascade);
modelBuilder.Entity<FriendRequest>()
.HasOne(r => r.Receiver)
.WithMany(u => u.RecevedRequests)
.OnDelete(DeleteBehavior.ClientCascade);
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<FriendRequest> FriendRequests { get; set; }
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
PrepareUserToDeleting();
return base.SaveChangesAsync();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
PrepareUserToDeleting();
return base.SaveChanges();
}
private void PrepareUserToDeleting()
{
// For each deleted user entity
foreach(var entry in ChangeTracker.Entries<ApplicationUser>().Where(e => e.State == EntityState.Deleted))
{
var user = entry.Entity;
// If RecevedRequests isn't loaded
if (user.RecevedRequests == null)
{
//Then load RecevedRequests
entry.Collection(u => u.RecevedRequests).Load();
}
// Idem with RequestedRequests
if (user.RequestedRequests == null)
{
entry.Collection(u => u.RequestedRequests).Load();
}
}
}
}
I have two database tables called
SegmentSet
SegmentSetGeometry
They both have primary key called SegmentSetId. (The SegmentSetGeometry (dependent) segmentSetId is foreign key to SegmentSet (primary)).
SegmentSets can have 0 or 1 SegmentSetGeometries
I have two classes representing these tables called SegmentSet and SegmentSetGeometry:
public class SegmentSet
{
public long SegmentSetId { get; set; }
// ...
public virtual SegmentSetGeometry SegmentSetGeometry { get; set; }
}
public class SegmentSetGeometry
{
public long SegmentSetId { get; set; }
public DbGeometry Geometry { get; set; }
public virtual SegmentSet SegmentSet { get; set; }
}
Here are their configurations:
public class SegmentSetConfiguration: EntityTypeConfiguration<SegmentSet>
{
public SegmentSetConfiguration()
{
ToTable("SegmentSet");
HasKey(x => x.SegmentSetId);
// ...
HasOptional(x => x.SegmentSetGeometry)
.WithRequired(x => x.SegmentSet);
}
}
public class SegmentSetGeometryConfiguration : EntityTypeConfiguration<SegmentSetGeometry>
{
public SegmentSetGeometryConfiguration()
{
ToTable("SegmentSetGeometry");
HasKey(x => x.SegmentSetId);
}
}
When attempting to get a SegmentSet from the database the following error shows up:
Invalid column name 'SegmentSet_SegmentSetId'.
I found this reference https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/fluent/relationships and decided to switch the relationship and tried:
public class SegmentSetConfiguration: EntityTypeConfiguration<SegmentSet>
{
public SegmentSetConfiguration()
{
ToTable("SegmentSet");
HasKey(x => x.SegmentSetId);
// ...
}
}
public class SegmentSetGeometryConfiguration : EntityTypeConfiguration<SegmentSetGeometry>
{
public SegmentSetGeometryConfiguration()
{
ToTable("SegmentSetGeometry");
HasKey(x => x.SegmentSetId);
HasRequired(x => x.SegmentSet)
.WithOptional(x=>x.SegmentSetGeometry);
}
}
But still doesn't work. I am using the example they give and it's not working, and I have looked at all the similar stack overflows and still not working
To map these two together, you should map from the required side of the relationship, and only that one side. Mapping both sides can lead to EF getting a bit garbled which may be causing your issue.
public class SegmentSet
{
public long SegmentSetId { get; set; }
// ...
public virtual SegmentSetGeometry Geometry { get; set; }
}
public class SegmentSetGeometry
{
public long SegmentSetId { get; set; }
// ...
public virtual SegmentSet SegmentSet { get; set; }
}
public class SegmentSetConfiguration: EntityTypeConfiguration<SegmentSet>
{
public SegmentSetConfiguration()
{
ToTable("SegmentSets");
HasKey(x => x.SegmentSetId);
// Note: Do not map the HasOptional here... Only map the required on the other side.
}
}
public class SegmentSetGeometryConfiguration : EntityTypeConfiguration<SegmentSetGeometry>
{
public SegmentSetGeometryConfiguration()
{
ToTable("SegmentSetGeometries");
HasKey(x => x.SegmentSetId);
HasRequired(x => x.SegmentSet)
.WithOptional(x=>x.Geometry);
}
}
That should work as expected using the PK's on each table for the 1..0/1 relationship.
Edit: It's also worth checking that the Entity configurations are being loaded. That might explain EF falling back on convention. In the DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(GetType().Assembly);
}
The above assumes the Entity Type Configs are in the same assembly as the DbContext definition. Otherwise: typeof(SegmentSetGeometryConfiguration).Assembly would do the trick.
When ever I pull a MyList object via EF, the parent is associated correctly but the Children collection is always null. Not sure what I'm doing wrong, pretty much every article shows to do it this way.
Database
CREATE TABLE [dbo].[MyList] (
[MyListId] BIGINT IDENTITY (1, 1) NOT NULL,
[ParentMyListId] BIGINT NULL,
CONSTRAINT [PK_MyList] PRIMARY KEY CLUSTERED ([MyListId] ASC) WITH (FILLFACTOR = 90),
CONSTRAINT [FK_MyList_MyList_MyListId] FOREIGN KEY (ParentMyListId) REFERENCES MyList(MyListId)
);
Model
public class MyList
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MyListId { get; set; }
public long? ParentMyListId { get; set; }
[ForeignKey("ParentMyListId")]
public virtual List MyListParent { get; set; }
public virtual ICollection<MyList> MyListChildren { get; set; }
}
DBContext
public class MyContext : DbContext
{
public MyContext() : base(Properties.Settings.Default.DbContext)
{
Configuration.LazyLoadingEnabled = false;
}
public DbSet<MyList> MyLists { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyList>()
.ToTable("MyList", "dbo")
.HasOptional(x => x.MyListParent)
.WithMany(x => x.MyListChildren)
.HasForeignKey(x => x.ParentMyListId)
.WillCascadeOnDelete(false);
}
base.OnModelCreating(modelBuilder);
}
I tried with same structure in EF 6.1.3 version and it worked like charm. I added image of output and data present in db. The only thing that might stopped working if you disable loading in configuration. I hope it work for you please try my sample code.
// Your entity class I added name property to show you the results
public class MyList
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MyListId { get; set; }
public long? ParentMyListId { get; set; }
public string Name { get; set; }
[ForeignKey("ParentMyListId")]
public virtual MyList MyListParent { get; set; }
public virtual ICollection<MyList> MyListChildren { get; set; }
}
// DBContext please note no configuration properties set just default constructor
// you need t check here if you have set soemthing here
public class TestContext : DbContext
{
public TestContext()
: base("name=TestConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyList>()
.ToTable("MyList", "dbo")
.HasOptional(x => x.MyListParent)
.WithMany(x => x.MyListChildren)
.HasForeignKey(x => x.ParentMyListId)
.WillCascadeOnDelete(false);
}
public virtual DbSet<MyList> Lists { get; set; }
}
The console app to show result:
static void Main(string[] args)
{
using (var ctx = new TestContext())
{
// for testing to see al working
//this is important to read the entity first .
var parent = ctx.Lists.ToList();
foreach (var p in parent)
{
foreach (var child in p.MyListChildren)
{
Console.WriteLine(string.Format(#"Parent Name {0} has child with name {1}", p.Name, child.Name));
}
}
}
Console.ReadLine();
}
Output of app and data in database ...
I am trying to implement two DbContexts which map to two seperate schemas/databases within MySql with a foreign key between them.
I understand questions similar to this have been asked before, but I can't find an answer in relation to MySql
I am using code first but and I'm getting the following error when I do Update-Database:
MultipleDbContext.ApplicationUser: : EntityType 'ApplicationUser' has no key defined. Define the key for this EntityType.
ApplicationUsers: EntityType: EntitySet 'ApplicationUsers' is based on type 'ApplicationUser' that has no keys defined.
These are my 2 DbContexts:
ApplicationDbContext
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext() : base("ApplicationDBContext") {}
public DbSet<Application> Applications { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ApplicationConfiguration());
}
}
public class ApplicationConfiguration : EntityTypeConfiguration<Application>
{
public ApplicationConfiguration()
{
HasKey(x => x.ApplicationID);
Property(x => x.ApplicationID).IsRequired();
Property(x => x.ApplicationName).IsRequired();
HasRequired(x => x.PrimaryUser).WithMany().HasForeignKey(x => x.UserID);
}
}
ApplicationUserDbContext
public class ApplicationUserDbContext : DbContext
{
public ApplicationUserDbContext() : base("UserDBContext") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ApplicationUserConfiguration());
}
}
public class ApplicationUserConfiguration : EntityTypeConfiguration<ApplicationUser>
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public ApplicationUserConfiguration()
{
HasKey(x => x.UserID);
Property(x => x.UserID).IsRequired();
Property(x => x.Name).IsRequired();
}
}
This is my update database statement:
update-database -ConfigurationTypeName MultipleDbContext.Migrations.Configuration
Thanks!
EDIT - Adding Entity Objects
public class Application
{
public int ApplicationID { get; set; }
public string ApplicationName { get; set; }
public int UserID { get; set; }
public virtual ApplicationUser PrimaryUser { get; set; }
}
public class ApplicationUser
{
public int UserID { get; set; }
public string Name { get; set; }
}
Problem probably is, that you have string property as a the primary key for your User and the length of this key is too long for MySql. I believe the limitation is 96 characters for keys (767 bytes).
A way to handle this would be to take only a subset of the string property and apply this to the key. To make the point clear, the following shows a key, which is only 4 chars long.
CREATE TABLE IF NOT EXISTS `foo` (
`id` varchar(128),
PRIMARY KEY (`id`(4)),
)
I'd suggest going all in with integer primary keys for the Aspnet.Identity stack - and also make tablenames all lowercase, since this would be an issue on mysql server hosted via case-sensitive filesystems.
This GitHub repo is kinda overkill as an example, but im pointing out a few points in the code here
Also this answer has a nice walkthrough
I have the following Entities which I am persisting using EntityFramework CodeFirst:
public class User {
RedGroup RedGroup { get; protected set; }
virtual ICollection<GreenGroup> GreenGroups { get; }
int Id { get; protected set; }
int? RedGroupId { get; protected set; }
}
public abstract class Group {
int Id { get; protected set; }
virtual ICollection<User> Users { get; protected set; }
}
public class RedGroup : Group {
// Other properties
}
public class GreenGroup : Group {
// Other properties
}
Essentially, the user can belong to zero or one red groups, and more than one green group. Each group has a collection of users that belong to it.
I am trying to set up EF using CodeFirst with TPT and am having trouble sorting the mappings. At the moment, I have the following in OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new RedGroupMap());
modelBuilder.Configurations.Add(new GreenGroupMap());
modelBuilder.Configurations.Add(new UserMap());
}
These are the mapping classes:
public abstract class GroupMap<T> : EntityTypeConfiguration<T>
where T : Group {
public GroupMap() {
this.ToTable("Groups");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
// Also has other non-relationship mappings
}
}
public class RedGroupMap() : GroupMap<RedGroup> {
public RedGroupMap() {
this.ToTable("RedGroups");
// Also has other non-relationship mappings
}
}
public class GreenGroupMap() : GroupMap<GreenGroup> {
public GreenGroupMap() {
this.ToTable("GreenGroups");
this.HasMany(c => c.Users)
.WithMany(p => p.GreenGroups)
.Map(m =>
{
m.MapLeftKey("GreenGroupId");
m.MapRightKey("UserId");
m.ToTable("Users_GreenGroups");
});
// Also has other non-relationship mappings
}
}
public class UserMap() : EntityTypeConfiguration<User> {
this.ToTable("Users");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
this.HasOptional(t => t.RedGroup)
.WithMany(t => t.Users)
.Map(x => x.MapKey("RedGroupId"))
.WillCascadeOnDelete(false);
}
I am getting the following runtime error:
Users: FromRole: NavigationProperty 'Users' is not valid. Type 'RedGroup' of FromRole 'User_RedGroup_Target' in AssociationType 'User_RedGroup' must exactly match with the type 'GreenGroup' on which this NavigationProperty is declared on.
Afraid I'm stumped on how to set up this.
How can I set up the EntityFramework mappings to allow a Table per Type hierarchy?
I created a context without your mappings, but with a much simpler configuration and everything appeared to create OK:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Group>().ToTable("Groups");
modelBuilder.Entity<RedGroup>().ToTable("RedGroups");
modelBuilder.Entity<GreenGroup>().ToTable("GreenGroups");
}
I've noticed that you've defined [User].HasOptional(t => t.RedGroup), but the RedGroupId field on User is defined as int and not int? - perhaps this is related?
public class User {
int? RedGroupId { get; protected set; } // int -> int?
RedGroup RedGroup { get; protected set; } // virtual missing, but shouldn't matter
// Other properties
}
If RedGroup is required, try using .HasRequired instead.