I have a principal entity AppUser and a dependent entity Account.
The following code shows a one-to-many relationship between them:
public class AppUser : IdentityUser
{
public ICollection<Account> Accounts { get; set; } = new List<Account>();
}
public class Account
{
public Guid Id { get; set; }
public AppUser User { get; set; }
public string UserId { get; set; }
}
My OnModelCreating method looks like this:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Account>()
.HasOne(x => x.User)
.WithMany(u => u.Accounts)
.HasForeignKey(fk => fk.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
Now if I have a AppUser object and want to remove one of its accounts like
user.Accounts.Remove(account);
This only removes the foreign key reference in that account entry. How can I make sure it fully deletes the entry in the database?
Call Remove on the corresponding DbSet (assuming names of variable and the property):
_context.Accounts.Remove(account);
Also possibly you can consider marking the parent entity as required (see this and this):
builder.Entity<Account>()
.HasOne(x => x.User)
.WithMany(u => u.Accounts)
.HasForeignKey(fk => fk.UserId)
.IsRequired(true)
.OnDelete(DeleteBehavior.Cascade);
Related
Imagine these models are a part of a social network application. User can make a Group and can add other users in it(as members) and a user can be a member of the group. I'm using .net core and also entity framework core.
Models:
public class User : BaseEntity
{
public string UserName { get; set; }
public IList<Group> OwnGroups { get; set; }
public IList<GroupMember> MemberInGroups { get; set; }
}
public class Group : BaseEntity
{
public string Name { get; set; }
public int OwnerUserId { get; set; }
[ForeignKey("OwnerUserId")]
public User OwnerUser { get; set; }
public IList<GroupMember> Members { get; set; }
}
public class GroupMember : BaseEntity
{
public int GroupId { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("UserId")]
public Group Group { get; set; }
}
Fluent API:
modelBuilder.Entity<User>()
.HasMany(x => x.OwnGroups)
.WithOne(x => x.OwnerUser).HasForeignKey(x => x.OwnerUserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<User>()
.HasMany(x => x.MemberInGroups)
.WithOne(x => x.User).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Group>()
.HasMany(x => x.Members)
.WithOne(x => x.Group).HasForeignKey(x => x.GroupId).IsRequired().OnDelete(DeleteBehavior.Cascade);
When I want to migrate to the database, this error happens:
Introducing FOREIGN KEY constraint 'FK_GroupMembers_Users_UserId' on table 'GroupMembers' 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.
However, I can make this model in SQL Server manually and there is no problem.
There are at least two ways to fix it. But I want to know why EF Core says, there is a cycle. What's the problem is?
I think your bridge table(junction) has a problem.
Try this
public class GroupMember: BaseEntity
{
public User User { get; set; }
[Key, Column(Order = 0)]
public int UserId { get; set; }
public Group Group { get; set; }
[Key, Column(Order = 1)]
public int GroupId { get; set; }
}
First of all: Get rid of the Fluent API part
//modelBuilder.Entity<User>()
//.HasMany(x => x.OwnGroups)
//.WithOne(x => x.OwnerUser).HasForeignKey(x => x.OwnerUserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<User>()
// .HasMany(x => x.MemberInGroups)
// .WithOne(x => x.User).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//modelBuilder.Entity<Group>()
// .HasMany(x => x.Members)
// .WithOne(x => x.Group).HasForeignKey(x => x.GroupId).IsRequired().OnDelete(DeleteBehavior.Cascade);
And then, based on Microsoft Docs, choose one of these cases:
Change one or more of the relationships to not cascade delete(make it nullable)
public int? OwnerUserId { get; set; }
The second approach instead, we can keep the OwnerUserId relationship required(non-nullable) and configured for cascade delete, but make this configuration only apply to tracked entities, not the database.
modelBuilder
.Entity<User>()
.HasMany(e => e.OwnGroups)
.WithOne(e => e.OwnerUser)
.OnDelete(DeleteBehavior.ClientCascade);
In this way, if we want to delete a User and all its Groups, we should load both the User and its Groups in the application(RAM). Look at this:
After running this code, the User and all its Groups will be removed.
var context = new MyDbContext();
var user = context.Users.Single(x => x.UserName == "SampleName");
var groups = context.Groups.Where(x => x.OwnerUser == user).ToList();
context.Users.Remove(user);
context.SaveChanges();
But the code below will throw an exception.
var context = new MyDbContext();
var user = context.Users.Single(x => x.UserName == "SampleName");
context.Users.Remove(user);
context.SaveChanges();
Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Groups_Users_OwnerUserId' on table 'Groups' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
User-Friend relationship
I find an answer
Entity Framework Core: many-to-many relationship with same entity
and try like this.
Entitys:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
public class Friend
{
public int MainUserId { get; set; }
public User ManUser { get; set; }
public int FriendUserId { get; set; }
public User FriendUser { get; set; }
}
The fluent API:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.ManUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
When I Add-Migration, the error message is
Cannot create a relationship between 'User.Friends' and 'Friend.FriendUser', because there already is a relationship between 'User.Friends' and 'Friend.ManUser'.
Navigation properties can only participate in a single relationship.
What should I do? Or I should create an Entity FriendEntity:User?
The problem is that you can't have one collection to support both one-to-many associations. Friend has two foreign keys that both need an inverse end in the entity they refer to. So add another collection as inverse end of MainUser:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> MainUserFriends { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
And the mapping:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany(mu => mu.MainUserFriends)
.HasForeignKey(f => f.MainUserId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
One (or both) of the relationships should be without cascading delete to prevent multiple cascade paths.
It's not mandatory the second collection. You only need to left de .WithMany() empty like this:
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany()
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany()
.HasForeignKey(f => f.FriendUserId);
look at this : https://github.com/aspnet/EntityFramework/issues/6052
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.
I have an entity which has a self reference such that a Member can have a Witness who has to be a member and may also have a Reference who has to be a member. I modeled this as follows;
public class Member
{
public int Id { get; set; }
//omitted for brevity
public int? WitnessId { get; set; }
public virtual Member Witness { get; set; }
public int? ReferenceId { get; set; }
public virtual Member Reference { get; set; }
}
When I run the update-database on package manager console, I get the following error:
"XXX.Client.Entities.Member' and 'XXX.Client.Entities.Member'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
Any idea on how this can be resolved?
Try to define relationship with fluent api this way (works for me):
modelBuilder.Entity<Member>().HasKey(x => x.Id);
modelBuilder.Entity<Member>().HasOptional(x => x.Witness)
.WithMany()
.HasForeignKey(m => m.WitnessId);
modelBuilder.Entity<Member>().HasOptional(x => x.Reference)
.WithMany()
.HasForeignKey(m => m.ReferenceId);
This looks to be working:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>().
HasOptional(e => e.Witness).
WithMany().
HasForeignKey(m => m.WitnessID);
modelBuilder.Entity<Member>().
HasOptional(e => e.Reference).
WithMany().
HasForeignKey(m => m.ReferenceID);
base.OnModelCreating(modelBuilder);
}
This will also work for those of us who prefer to have things in the class deriving from the EntityTypeConfiguration
class MemberEntityConfiguration : EntityTypeConfiguration<Member>
{
public MemberEntityConfiguration()
{
HasKey(x => x.Id);
HasOptional(x => x.Witness).WithMany().HasForeignKey(m => m.WitnessId);
HasOptional(x => x.Reference).WithMany().HasForeignKey(m => m.ReferenceId);
}
}
I have this entity:
public class MyEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
I want this entity to mapped into Oracle an oracle 11g database as MySchema.MyEntity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>().ToTable("MyEntity", "MySchema");
base.OnModelCreating(modelBuilder);
}
The problem is that when I try to add an entity ,and do SaveChanges, it completely ignores the schema part of the ToTable(), even if I add a [Table("MySchema.MyEntity") ] attribute to the class it is also ignored. Schema will always be the login name of the connnection string regardless of what I do.
DbConnection con = new Devart.Data.Oracle.OracleConnection(
"User Id=system;Password=admin;Server=XE;Persist Security Info=true;");
The schema name is always what I set as UserId. It only changes if I write down explicitly that:
con.ChangeDatabase("MySchema"); //this will only work if the database connection is open...
But I do now want to write this down...
How to make this work?
EDIT:
Oh man... Solution :
First : UPPERCASESCHEMANAMES!!!
Second: In the offical dotconnect example there is a line:
config.Workarounds.IgnoreSchemaName = true;
Delete it... (this will only work if you set the schema name for ALL your entities, otherwise a "dbo" schema will be used that does not exist in oracle... )
kori0129, your solution is correct. The corresponding blog article is here: http://www.devart.com/blogs/dotconnect/index.php/entity-framework-code-first-support-for-oracle-mysql-postgresql-and-sqlite.html .
If you encounter any difficulties with the dotConnect for Oracle functionality, please contact us via http://www.devart.com/company/contact.html .
I used the ConnectionString to get the SCHEMA
Here's my solution:
public class MyContext : DbContext
{
private string oracleSchema;
public MyContext()
: base("OracleConnectionString")
{
Database.SetInitializer<MyContext>(null);
oracleSchema = new System.Data.SqlClient.SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["OracleConnectionString"].ConnectionString).UserID.ToUpper();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().ToTable(string.Format("{0}.{1}", oracleSchema, "CUSTOMER"));
modelBuilder.Entity<Invoice>().ToTable(string.Format("{0}.{1}", oracleSchema, "INVOICE"));
modelBuilder.Entity<Product>().ToTable(string.Format("{0}.{1}", oracleSchema, "PRODUCT"));
modelBuilder.Entity<Category>().ToTable(string.Format("{0}.{1}", oracleSchema, "CATEGORY"));
modelBuilder.Entity<Item>().ToTable(string.Format("{0}.{1}", oracleSchema, "ITEM"));
modelBuilder.Entity<Invoice>().HasRequired(p => p.Customer);
modelBuilder.Entity<Item>().HasRequired(p => p.Invoice);
modelBuilder.Entity<Item>().HasRequired(p => p.Product);
modelBuilder.Entity<Product>()
.HasMany(x => x.Categories)
.WithMany(x => x.Products)
.Map(x =>
{
x.ToTable("ASS_CATEGORY_PRODUCT", oracleSchema);
x.MapLeftKey("ID_CATEGORY");
x.MapRightKey("ID_PRODUCT");
});
modelBuilder.Entity<Category>()
.HasMany(x => x.Products)
.WithMany(x => x.Categories)
.Map(x =>
{
x.ToTable("ASS_CATEGORY_PRODUCT", oracleSchema);
x.MapLeftKey("ID_PRODUCT");
x.MapRightKey("ID_CATEGORY");
});
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<Item> Items { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}