I have two DbContext in my application, which configure some models with Fluent API. One model of my first DbContext has a foreign key to a second model configured in my second DbContext.
public class UserData
{
public double Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName {
get {
return $"{this.FirstName} {this.LastName}";
}
}
public string Adress1 { get; set; }
public virtual BaseUserTreeData BaseUserTree { get; set; }
public double? BaseUserTreeId { get; set; }
public virtual List<DeviceData> Devices { get; set; }
}
The model BaseUserTreeData is my foreign property configured in my second DbContext.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BaseUserTreeData>().ToTable("sw_data_baseusertree");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.Id).HasColumnName("baseusertree_ID");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.Label).HasColumnName("label");
modelBuilder.Entity<BaseUserTreeData>().Property(baseusertree => baseusertree.ParentTreeId).HasColumnName("baseUserTree_ID_parent");
modelBuilder.Entity<BaseUserTreeData>().HasKey(baseusertree => baseusertree.Id);
modelBuilder.Entity<BaseUserTreeData>()
.HasOptional(tree => tree.ParentTree)
.WithMany(tree => tree.ChildTrees)
.HasForeignKey(tree => tree.ParentTreeId);
}
When I use UserData with my UserDbContext for the first time, the OnModelCreating of my second DbContext is not called, so BaseUserTreeData mapping is not executed, and the query generated by Entity Framework 6 is wrong. I saw that I can share EntityConfiguration in separate classes, but is there a way to tell to EF6 to call every OnModelCreating of all my DbContext?
I think you're in wrong path.This is not a recommended way of handling Context.If there is too strong relationship between models, you have to concentrate the models inside one unique context.Otherwise you'll have to face so many issues in the future.So my advice is to use the pattern which EF team suggested below.
This is the way Microsoft EF Team has suggested :
When working with Web applications, use a context instance per
request.
You can read more about context handling using below articles :
Working with DbContext
Managing DbContext the right way with Entity Framework
Related
I am studying EF Core with database first. There is no issue to get entities and DbContext after reverse-engineering. But I couldn't understand the role(or purpose) OnModelCreating Method in DbContext(database first approach).
Here is code snippet.
public partial class VitiLevuContext : DbContext
{
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Invoice> Invoices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>(entity =>
{
entity.ToTable("Invoice");
entity.Property(e => e.DueAmount)
.IsRequired();
entity.Property(e => e.PaidAmount).HasColumnType("money");
entity.HasOne(d => d.Order)
.WithMany(p => p.Invoices)
.OnDelete(DeleteBehavior.Cascade)
.HasForeignKey(d => d.OrderId)
.HasConstraintName("FK__Invoice__OrderId__44FF419A");
});
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Order");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
Database has a relation and "NOT NULL Contraints".
ALTER TABLE [dbo].[Invoice] ADD FOREIGN KEY ([OrderId]) REFERENCES [dbo].Order ON DELETE CASCADE.
ALTER TABLE [dbo].[Invoice] ADD [DueAmount] int NOT NULL
The OnModelCreating method represents well. I created very simple Rest API project and tested add/delete for Order/Invoice.
"NOT NULL Constraints" and "Cascade deleting" may be verified on database not EF model side.
(In case of creating an invoice instance with null DueAmount, I expected exceptions before submitting to SQL)
My question is very simple.
Can I delete "OnModelCreating" method if don't consider migration?
(I thought the OnModelCreating method is only for migration purpose.)
If you follow the Entity framework model naming convention and your model directly reflects your database table name, column names and so on, you don't need the OnMOdelCreating method. This is because the entity framework will generate the binding behind the scene.
But, if you want customization, for example, your model field name does not match your database table column name, you configure that on the OnModelCreating method. Another way of using this configuration is called fluent API.
This doesn't mean you have to use the OnModelCreating method. There are other options for customization. Which is DataAnotation.
For example:
If you have a model named User...
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Password { get; set; }
}
on your DbContext, you set the following
public AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) {}
public DbSet<User> Users { get; set; }
}
So, by convention, the Entity framework expects
A table named Users, because of the name you used on the DbSet property for the User model.
It uses Id as the primary key.. because the model property name Id
Entity framework will set this all up for you.
When we come to the custom configuration, let's say your model property name Password is not the same as the Users table column name Pwd. You have to tell the entity framework in one of the following ways.
using the OnModelCreating method (fluent API)
protected override void OnModelCreating(ModelBuilder modelBuild)
{
modelBuilder.Entity<User>(entity => {
entity.Property(p => p.Password)
.HasColumnName("Pwd");
})
}
The other way is Data annotation
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
[Column("Pwd")]
public string Password { get; set; }
}
I have two entities that represent a User of the web application, and a Participant in the podcast that the web application is all about. A user of the web page has a profile, can log in, can leave comments etc. A Participant is linked from the episode objects they appear in, has a bio, a picture etc. It is possible to be both a Participant and a User at the same time, but it is also possible to be just a user, or just a participant.
I'm struggling to model this in EF Core 3.1. If it matters, I am also using .Net Core 3.0 for this project, and the database is Postgresql (using the Nuget package Npgsql.EntityFrameworkCore.PostgreSQL v3.1.0).
On both sides this relationship should be nullable/non-required. The entities are pretty simple (all non-importart properties omitted):
User:
public class User
{
public Guid UserId { get; set; }
public Participant Participant { get; set; }
public Guid ParticipantId { get; set; }
}
Participant:
public class Participant
{
public Guid ParticipantId { get; set; }
public User User { get; set; }
public Guid UserId { get; set; }
}
I am trying to use the Fluent API to configure the relationships - this seems to be where it breaks down.
User config:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> user)
{
user.ToTable("users");
user.HasKey(u => u.UserId);
user.HasOne(u => u.Participant)
.WithOne(p => p.User)
.HasForeignKey<Participant>(p => p.UserId);
}
}
Participant config:
public class ParticipantConfiguration : IEntityTypeConfiguration<Participant>
{
public void Configure(EntityTypeBuilder<Participant> participant)
{
participant.ToTable("participants");
participant.HasKey(p => p.ParticipantId);
participant.HasOne<User>(p => p.User)
.WithOne(u => u.Participant)
.HasForeignKey<User>(u => u.ParticipantId);
}
}
Now I realize that you should only configure one side of the relationship - at least that is how I interpret what I have read. I have just included the above for completeness; I have tried configured both sides at once as above, I have tried doing it only on the User side, and only on the Participant side. In every combination, the code compiles, and the application starts up, but when I try actually adding a User to the database through the DbContext, I get the same exception:
System.InvalidOperationException: 'The child/dependent side could not be determined for the one-to-one relationship between 'Participant.User' and 'User.Participant'. To identify the child/dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship configure them without specifying the inverse. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.'
Now, these are two completely independent objects that happen to know of each other, so I am not sure the mindset of child/dependent is accurate, but I am willing to ignore that detail to bend to EF Core's will; However, I can't figure out how to let me get this working without the exception.
TL;DR:
A User can have a Participant link.
A Participant can have a User link.
It is perfectly fine for either for that link to be NULL.
How do I configure EF Core for this?
Thanks for any insights!
First thing to do i think is to change foreign keys into nullables.
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid UserId { get; set; }
public Participant Participant { get; set; }
public Guid? ParticipantId { get; set; }
}
public class Participant
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ParticipantId { get; set; }
public User User { get; set; }
public Guid? UserId { get; set; }
}
And then keep your configurations intact. My sample working configuration :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasOne(t => t.Participant)
.WithOne(t => t.User)
.HasForeignKey<Participant>(t => t.UserId);
modelBuilder.Entity<Participant>().HasOne(t => t.User)
.WithOne(t => t.Participant)
.HasForeignKey<User>(t => t.ParticipantId);
base.OnModelCreating(modelBuilder);
}
I would double check that your configuration is being called. Everything looks like it should work.
I am developing an application following the DDD pattern.
I have the following contexts for employee management and user account management called
EmployeeManagementContext
and
UserAccountManagementContext
Both contexts are on a separate project.
The project for employee management has the following models.
public class Employee
{
public int Id { get; private set; }
public string Name { get; private set; }
public DateTime DateOfBirth { get; private set; }
}
The project for user account management has the following models.
public class Employee
{
public int Id { get; private set; }
public string Name { get; private set; }
}
public class UserAccount
{
public int Id { get; private set; }
public string Username { get; private set; }
public string Password { get; private set; }
}
EmployeeManagementContext
public class EmployeeManagementContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
UserAccountManagementContext
public class UserAccountManagementContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
}
I can successfully migrate both context by having different context keys but the problem is I loose the relationship between the Employee and UserAccount models.
Basically, the business rules that I need to implement between the two models are as follow:
An Employee may or may not have a UserAccount.
A UserAccount is owned by exactly one Employee only.
This means that I should have a one to zero-or-one relationship between Employee and UserAccount like the diagram below.
Please disregard the wrong relationship notation, its seems to be a limitation of the tool I am using but it is a one-to-zero-or-one relationship I assure you.
I tried the following configurations in UserAccount project:
public class UserAccountConfiguration : EntityTypeConfiguration<UserAccount>
{
HasKey(x => x.Id);
Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
HasKey(x => x.Id);
Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasOptional(x => x.UserAccount)
.WithRequired(x => x.Employee);
}
public class UserAccountManagementContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserAccountConfiguration());
modelBuilder.Configurations.Add(new EmployeeConfiguration());
base.OnModelCreating(modelBuilder);
}
}
The above configurations result to an error because Employee table already exist because of the EmployeeManagementContext in employee management project.
If I try to add the following configuration in EmployeeConfiguration of employee management project,
ToTable("Users");
EF doesn't complain anymore and creates a Users table which then creates the relationship I need between Employee and UserAccount. But the problem is, if I try to query Employee/Users in UserAccountManagementContext, it doesn't contain anything and I don't think its good design to keep on creating smaller tables that is derived from the orginal table because it will only clutter the database, right?...
I would greatly appreciate your help, thanks.
You should focus more on the domain and less on the database.
From what I can see, you have two Aggregates (Employee and UserAccount), in possible 2 bounded contexts (I can't name them as I don't have enough data). In general it's not recommended to force any invariant in a strongly consistent manner between the two Aggregates but there are exceptions. They may be as well in different databases, having different technologies. Let's now see how you can enforce the two invariants:
An Employee may or may not have a UserAccount.
This can be modeled with a nullable UserAccountId on a Employee, without any low level database references. Depending on the business rules, when an UserAccound is deleted (if this is a valid business operation on it), using a Saga/Process manager, you can set to null the corresponding UserAccountId in the Employee that had this account.
A UserAccount is owned by exactly one Employee only.
The simplest way to enforce this invariant is of technological nature: create an unique index on the UserAccountId. Other solutions imply using Sagas but are not as good as this one, for example would permit for a short period of time for the invariant to be broken.
I'm trying to create a 'one to many' relation between the classes 'ApplicationUser' and one recently created called 'Issue'.
So, in Models / IdentityModels.cs / ApplicationUser i added this property:
public ICollection<Issue> Issues { get; set; }
And Issue.cs has this code:
namespace Test.Models
{
public class Issue
{
public int Id { get; set; }
public ApplicationUser Courier { get; set; }
public ApplicationUser Customer { get; set; }
}
}
I'm using automatic migrations. So, after building and running 'update-database', the Issues table was created with these fields:
Id
ApplicationUser_Id
CourierId
CustomerId
My question is why was the field 'ApplicationUser_Id ' created and how can i prevent it?
The problem is that EF thinks you actually want three one-to-many relationships between Issue and ApplicationUser:
one for ICollection<Issue> Issues on ApplicationUser (ApplicationUser_Id)
one for ApplicationUser Courier on Issue (CourierId)
one for ApplicationUser Customer on Issue (CustomerId)
(Note that EF allows to define relationships from either side.)
If you want that ApplicationUser.Issues contains all Issues of this User, whether he is a Courier or Customer, you will need the additional ApplicationUser_Id key. Configuring EF so that this works will be quite a pain.
Maybe a simpler solution will do: introduce two collections on ApplicationUser.
public ICollection<Issue> CourierIssues { get; set; }
public ICollection<Issue> CustomerIssues { get; set; }
And then configure the backlinks in the ModelBuilder using the fluent API to eliminate the ApplicationUser_Id key:
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CourierIssues).WithOptional(i => i.Courier);
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CustomerIssues).WithOptional(i => i.Customer);
Because you have two foreign keys to ApplicationUser, Courier and Customer, but only one collection referencing Issue on ApplicationUser. EF has no way of know which foreign key it should line up with, so it just created a new one. To handle this properly, you need to utilize fluent config:
public class ApplicationUser
{
...
public class Mapping : EntityTypeConfiguration<ApplicationUser>
{
HasMany(m => m.Issues).WithRequired(m => m.Customer);
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ApplicationUser.Mapping());
}
The problem here of course, is that you are likely wanting to track collections for both the Customer and Courier collections. For that, you need two collections:
public virtual ICollection<Issue> CustomerIssues { get; set; }
public virtual ICollection<Issue> CourierIssues { get; set; }
Then, the following fluent config:
HasMany(m => m.CustomerIssues).WithRequired(m => m.Customer);
HasMany(m => m.CourierIssues).WithRequired(m => m.Courier);
I have two models. ApplicationUser:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public DateTime AccountCreationDate { get; set; }
public virtual ICollection<ProfileView> ProfilesViewed { get; set; }
}
And ProfileView:
public class ProfileView
{
public int Id { get; set; }
public DateTime ViewDate { get; set; }
public virtual ApplicationUser Viewer { get; set; }
public virtual ApplicationUser Viewee { get; set; }
}
Entity framework seems to have created my tables correctly. I can do the following and retrieve a user's ProfileViews:
db.ProfileViews.Where(p => p.Viewer.Id == currentUser.Id);
My problem is that I can't seem to do the following:
db.Users
.Where(u => u.Id == currentUser.Id)
.Include(u => u.ProfilesViewed);
The above returns null for that user, even though it is a Viewer and a Viewee on several ProfileView.
I ran a foreach on all my users, none of them seem to have any ProfilesViewed if I query them from the Users table with Include. I can only retrieve ProfileViews from the ProfileViews table...
Anyone has any idea how to fix this?
Since you did not mention how the ProfileView.Viewer is related to the ApplicationUser.ProfileViewed EF thinks that they are not related(if you check your DB you can see another FK created in the ProfileView for the ApplicationUser.ProfileViewed collection). So adding instances to ProfileView does not effect the User.ProfilesViewed.
Add this code to the Context class, to specify that each ApplicationUser is related to many ProfileView through ProfilesViewed collection.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProfileView>().HasRequired(x => x.Viewer)
.WithMany(x => x.ProfilesViewed);
}
The relationships probably need to be explained to be created correctly. I think it's wrong because you have two relationships from ProfileView to ApplicationUser. See the section "Configuring Unconventional Foreign Key Names" in this MSDN article for details of how to configure unconventional relationships using EF Code First.