Link models from different databases - entity-framework

I have the following situation: I have a class Company, it is as simple as possible
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
}
I also need to extend the IdentityUser class from ASP. NET Identity Core:
public class User : IdentityUser
{
public Company Company { get; set; }
public int CompanyID { get; set; }
}
Not bad so far. But I also need a class Device :
public class Device
{
public Company Company { get; set; }
public int CompanyID { get; set; }
}
Accordingly, we add the Devices property of the List<Device> type to the Company class and Users property of the List<User> type.
The problem is that I want to separate the data (Device table) from the data of ASP .NET Identity Core in different databases. Table Company I want to put in the database with Auth data. And table Device in other one. How can I tell the Entity Framework that the Company property of the Device class is from another database?

If the Company property is from another database, you may need to add a second database context for it, if you haven't already. Something like this:
public class CompanyModelContext : DbContext
{
public CompanyModelContext(DbContextOptions options) : base(options){ }
public DbSet<Company> Company { get; set; }
}

You can try to set your EF model like this:
Companies table:
[Table("Companies")]
public class Company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Device> Devices { get; set; }
}
Devices table:
[Table("Devices")]
public class Device
{
[Key]
public int Id { get; set; }
public Company Company { get; set; }
public int CompanyID { get; set; }
}
ApplicationDbContext class:
public DbSet<Company> Companies { get; set; }
public DbSet<Device> Devices { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>(buildAction =>
{
buildAction.ToTable("Companies");
buildAction.HasMany(x => x.Devices)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
};
}
I've added Devices as a list in Company class, then overriding the FK in the method OnModelCreating.
Once you delete an item in table Company, all items with the same Id in table Devices (comparing to CompanyID) will be delted automatically.
Since we use the 2 properties [Table] and [Key] to define table name and the primary key. So in the OnModelCreating, you can ignore to define the name, PK, FK of table Device.
Company has many User
In this case:
User class:
public class User : IdentityUser
{
public int CompanyId { get; set; }
public Company Company { get; set; }
}
Company class:
[Table("Companies")]
public class Company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Device> Devices { get; set; }
public ICollection<User> Users { get; set; }
}
ApplicationDbContext class:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Company>(buildAction =>
{
buildAction.ToTable("Companies");
buildAction.HasMany(x => x.Devices)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
buildAction.HasMany(x => x.Users)
.WithOne(x => x.Company)
.OnDelete(DeleteBehavior.Cascade);
};
}

Related

EF Core Many-to-Many Relation Table Naming [duplicate]

This question already has answers here:
Change name of generated Join table ( Many to Many ) - EF Core 5
(2 answers)
Closed 1 year ago.
Does EF Core provide a way of naming the many-to-many relations mapping to database tables ?
In a code-first pattern, I have the following 2 Entities:
[Table("Prefix.Users")]
public class User
{
public int ID { get; set; }
public IEnumerable<Role> Roles { get; set; }
}
[Table("Prefix.Roles")]
public class Role
{
public int ID { get; set; }
public IEnumerable<User> Users { get; set; }
}
I've skipped the detailed Entity structure here. The ID properties in User & Role are keys (Database generated Identity)
User and Role entities share a many-to-many relationship.
EF Core generates a third table in Database with Table name UsersRoles
Is there a way I can add a prefix to the 3rd table name so it becomes Prefix.UsersRoles without manually adding a third Entity UserRoles that maps User and Role and giving it the desired name with Prefix
Use fluent API instead of using data annotations
Your model classes should be like this.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
Your fluent api configuration classes like be this
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User");
builder.HasKey(x => x.Id);
}
}
public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable("Role");
builder.HasKey(x => x.Id);
}
}
public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.ToTable("UserRole");
builder.HasKey(x => new { x.UserId, x.RoleId });
builder
.HasOne<Role>(s => s.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(s => s.RoleId).OnDelete(DeleteBehavior.Restrict);
builder
.HasOne<User>(s => s.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(s => s.UserId).OnDelete(DeleteBehavior.Restrict);
}
}
Your DbContext class should be like this
public class MyDbContext : DbContext
{
public EEGDbContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(#"Server=xxxx;Database=DB;User Id=sa;Password=xxxxx;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
}

Cannot Insert duplicate key on related table

My model has Owners and Complexes. An owner can have many complexes, and a complex could theoretically have multiple owners (joint ownership). I want to be able to create new complexes and owners independently, so neither should require the other. However, when I try to add a new complex, I get this error:
Violation of PRIMARY KEY constraint 'PK_dbo.Owners'. Cannot insert duplicate key in object 'dbo.Owners'. The duplicate key value is (fcd72b09-b1ef-4894-83de-cb4897c0c401).
The statement has been terminated.
For the record, there is currently one existing owner (with the ID mentioned in the error). The owner is already associated with another complex. I should be able to add a new complex with this owner, but obviously it's not allowing me to.
What do I need to change with my model to accomodate this? Relevant code follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Entity<Complex>().ToTable("Complex");
//modelBuilder.Entity<Unit>().ToTable("Unit");
//modelBuilder.Entity<Address>().ToTable("Addresses");
//modelBuilder.Entity<Tenant>().ToTable("Tenant");
modelBuilder.Entity<ContactInfo>().ToTable("Contacts");
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Entity<Complex>()
.HasOptional(x => x.Owner)
.WithMany(x => x.Complexes);
modelBuilder.Entity<Unit>()
.HasOptional(x => x.Complex)
.WithMany(x => x.Units);
modelBuilder.Entity<Owner>()
.HasMany(x => x.Complexes);
base.OnModelCreating(modelBuilder);
}
}
Owner and Complex models:
public class Owner
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? ContactInfoId { get; set; }
[ForeignKey("ContactInfoId")]
public ContactInfo ContactInfo { get; set; }
public ICollection<StaffMember> Employees { get; set; }
public ICollection<Complex> Complexes { get; set; }
public Owner()
{
this.Id = System.Guid.NewGuid();
this.Employees = new HashSet<StaffMember>();
this.Complexes = new HashSet<Complex>();
}
public void AddEmployee(StaffMember employee)
{
Employees.Add(employee);
}
public void AddComplex(Complex complex)
{
Complexes.Add(complex);
}
}
public class Complex
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? OwnerId { get; set; }
[ForeignKey("OwnerId")]
public Owner Owner { get; set; }
public Guid? AddressId { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
public virtual ICollection<Unit> Units { get; set; }
public virtual ICollection<StaffMember> StaffMembers { get; set; }
public Complex()
{
this.Id = System.Guid.NewGuid();
this.Units = new HashSet<Unit>();
this.StaffMembers = new HashSet<StaffMember>();
}
public void AddUnit(Unit unit)
{
Units.Add(unit);
}
public void AddStaff(StaffMember staffMember)
{
StaffMembers.Add(staffMember);
}
}
Your entities aren't setup correctly. In your Complex object, you are stating that it has only 1 owner so you're setting it up as a one to many instead of a many to many. If you set it as a collection instead of an object, EF will handle the many to many table for you

Navigation Properties using two foreign keys

I have a project with several tables in the same database.
public class UserImage
{
[Key]
public int Id { get; set; }
public string OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual ApplicationUser Owner { get; set; }
//some fields are removed for brevity
}
public class FriendRequest
{
[Key]
public int Id { get; set; }
public string UserId { get; set; }
public string FutureFriendUserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
[ForeignKey("FutureFriendUserId")]
public virtual ApplicationUser FutureFriendUser { get; set; }
}
public class ApplicationUser : IdentityUser
{
//some fields are removed for brevity
public virtual ICollection<UserImage> UserImages { get; set; }
public virtual ICollection<FriendRequest> FriendRequests { get; set; }
The problem is that I can find the images that belong to a user:
var userStore = new UserStore<ApplicationUser>(db);
var userManager = new UserManager<ApplicationUser>(userStore);
ApplicationUser user = userManager.FindById(User.Identity.GetUserId());
IEnumerable<string> imgs = (from image in user.UserImages select Url.Content(image.ImageUrl)).Skip(skip).Take(5).ToList();
but I can't use the same technique for the FriendRequests. If I search in the database for the rows that have UserId == User.Identity.GetUserId() or some other id, the results are what I expect.
What is the problem?
What you're essentially creating here is a self-referential many-to-many relationship. On your FriendRequest class, you have two properties that are foreign keys to ApplicationUser, but on your ApplicationUser class, you have only a single collection of FriendRequest. Entity Framework has no idea which foreign key should actually compose this collection. As a result, you have to make a few changes to get this working properly.
You must add another navigation property. Essentially, on your ApplicationUser class you'll end up with something like the following:
public virtual ICollection<FriendRequest> SentFriendRequests { get; set; }
public virtual ICollection<FriendRequest> ReceivedFriendRequests { get; set; }
Again, you need a collection for each foreign key.
You'll need to add some fluent config to help Entity Framework determine which foreign key to use for each collection:
public class ApplicationContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<FriendRequest>().HasRequired(m => m.User).WithMany(m => m.SentFriendRequests);
modelBuilder.Entity<FriendRequest>().HasRequired(m => m.FutureFriendUser).WithMany(m => m.ReceivedFriendRequests);
}

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table

EF4.1 How to I implement a CascadeDelete where two tables share records in one table?

I'm using EF4.1 Code First. I have two classes which both have a one-to-one relationship with a contact class. When I remove a record in either of the two classes I want the associated entry in the contact class removed also.
ex:
public class User
{
public virtual int ID { get; set; }
...
public virtual Contact Contact { get; set; }
}
public class Admin
{
public virtual int ID { get; set; }
...
public virtual Contact Contact { get; set; }
}
public class Contact
{
public virtual int ID { get; set; }
...
}
I tried various things with annotations and fluent API but could not yet manage to get a cascade delete working. What is the correct way to implement this in EF 4.1 Code First?
I'm not sure but I think your contact entity needs User and Admin entities also..
Then the fluent api should work:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasRequired(u => u.Contact)
.WithRequiredPrincipal(c => c.User)
.WillCascadeOnDelete();
modelBuilder.Entity<Admin>()
.HasRequired(a => a.Contact)
.WithRequiredPrincipal(c => c.Admin)
.WillCascadeOnDelete();
}
I think this should work if you want to delete Users and Admins if a Contact is deleted:
public class User
{
public int ID { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int ID { get; set; }
public virtual List<User> Users {get; set;}
public Contact()
{
Users = new List<User>();
}
}
While I believe you want to do the opposite, which means you need to make the Contact is the dependent entity
public class User
{
public int ID { get; set; }
public bool IsAdmin {get; set;}
public virtual List<Contact> {get; set;}
public Contact()
{
Users = new List<User>();
}
}
public class Contact
{
public int ID { get; set; }
[ForiegnKey("Owner")]
public int UserId {get; set;}
public virtual User Owner {get; set;}
}
you can use other types of Inheritance other than TPC I'm using here, because DBMS doesn't support two mutually exclusive Foreign Keys