How to create relationships between models that are found on different DbContext? - c#

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.

Related

One-to-one relationship in EF Core gives child/dependent exception

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.

ASP.NET MVC5 - unwanted field created after update database

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);

Discover all DbContext

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

Entity framework relationship broken

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.

How to have 2 related objects stored in different databases using Entity Framework

I'm redoing an old application using C#, .net 4.5, ASP.NET MVC 5 and Entity Framework 6.
Because this web app will be used at the same time the old app, it needs to share the same databases.
To save me some time I've generated my model based on the existing databases. (to do this I first gathered all tables in the same database, defined their relationships, generated the model based on that new database with every table and then use the old databases to run my code)
To clarify my problem I created this simple example.
A person can belong to many groups.
One table Persons is in DB1 and the table Groups is in DB2.
The relation between these 2 tables doesn't exist because they are in different databases. But I should be able to know to which Groups a Person belongs to and which persons are in a given group.
The generated code for the class persons is:
public partial class Persons
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Person Name")]
public string Name { get; set; }
public int GroupID { get; set; }
public virtual Groups Groups { get; set; }
}
and the Groups class:
public partial class Groups
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Groups()
{
Persons = new HashSet<Persons>();
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Group Name")]
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Persons> Persons { get; set; }
}
then I created 2 contexts one for each database
<add name="ModelContext1" connectionString="data source=PROG-PC\SQLEXPRESS;initial catalog=DB1;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
<add name="ModelContext2" connectionString="data source=PROG-PC\SQLEXPRESS;initial catalog=DB2;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
and the respective context classes (I'm not sure if I should keep the code related to the foreign keysm since they dont actually exist)
public partial class ModelContext1 : DbContext
{
public ModelContext1()
: base("name=ModelContext1")
{
}
public virtual DbSet<Persons> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Groups>()
.HasMany(e => e.Persons)
.WithRequired(e => e.Groups)
.HasForeignKey(e => e.GroupID)
.WillCascadeOnDelete(false);
}
}
public partial class ModelContext2 : DbContext
{
public ModelContext2()
: base("name=ModelContext2")
{
}
public virtual DbSet<Groups> Groups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Groups>()
.HasMany(e => e.Persons)
.WithRequired(e => e.Groups)
.HasForeignKey(e => e.GroupID)
.WillCascadeOnDelete(false);
}
}
My first problem is my PersonControler:
public class PersonsController : Controller
{
private ModelContext1 db = new ModelContext1();
private ModelContext2 db2 = new ModelContext2();
// GET: Persons
public ActionResult Index()
{
var persons = db.Persons.Include(p => p.Groups);
return View(persons.ToList());
}
(keep im mind I'm new to MVC and EntityFramework)
How can I solve this problem?
What other considerations should I take?
I realize that it's a year later, but since I stumbled upon this maybe it will help someone else in a similar situation. We have a user/client database that stores our Identity data for user and client logins, roles, etc... We then have our application database that stores the actual application data. We offer our clients the ability to physically partition their data and as part of a system rewrite we're looking for ways to keep our sanity as our client base grows. We decided to use EF 6 and needed a way to relate users to objects in the application, we're trying the solution I found here. The plan is to create a public synonym (in db2 in the example above) and use that to create the relationships in the Context Configuration. Comments on that answer seem positive, we're still working on it but I'll update this answer once we're up and running.

Categories

Resources