I have a advertiser model like:
public class Advertiser
{
public int AdvertiserId { get; set; }
public string Name { get; set; } = string.Empty;
public Address AddressId { get; set; }
}
Inside this class I have a builder as:
public class AdvertiserConfiguration : IEntityTypeConfiguration<Advertiser>
{
public void Configure(EntityTypeBuilder<Advertiser> builder)
{
builder.ToTable("Advertisers");
builder.HasKey(x => x.AdvertiserId);
builder.Property(x => x.Name).IsRequired().HasMaxLength(250);
builder.HasOne(x => x.AddressId);
}
}
And address model like:
public class Address
{
public int AddressId { get; set; }
....
}
So that I want to do is a simple foreign key on the Advertiser table so I check msdn reference
And it says that I should use HasOne and WithMany methods in order to use HasForeignKey, but I do not understand why? it is necessary to use them to do a simple foreign key connection? if yes, what fields should I use on HasOne and WithMany? Thanks!
In ef for a relation you define a "navigation property" on both sides of the related objects and a "foreign key property". So your entities should look like this
public class Advertiser
{
public int AdvertiserId { get; set; }
public Address? Address { get; set; }
public int AddressId { get; set; }
...
}
public class Address
{
public int AddressId { get; set; }
public virtual ICollection<Advertiser>? Advertisers { get; set; }
...
}
and your entity configuration
builder
.HasOne(adv => adv.Address)
.WithMany(adr => adr.Advertisers)
.HasForeignKey(adv => adv.AddressId);
That way you define which properties are the connected objects and how ef should resolve this from the database (by using the foreign key).
Now you can use code like this
foreach(var advertiser in address.Advertisers)
{
...
}
or
var street = advertiser.Address.Street;
...
You won't want to do all the navigation manually by requerying the database e. g. for the connected advertisers after you read an address.
Remember to Include navigation properties in your queries, when they will be used after/outside of the queries.
Related
I have an application where the domain has several parent entities each to which a list of Comments are linked in a master-detail fashion.
Instead of having a Comment table for each parent entity's comments, I have opted to have a single Comment table with links to the parent entities.
The reason for this choice is as follows:
As the application expands with more parent entities, I don't need to add a Comment table for each of them
In order to implement a search where the user can search through comments, it is likely to simply search through a single table (likely will use Lucene)
This is illustrated below
Happy to change my mind re this design choice
The link between the parent entity and Comment acts like a many-many relationship. The domain objects are defined as:
public class Customer
{
public Customer(string name)
{
Name = name;
}
protected Customer()
{}
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Comment> Comments { get; }
public void AddComment(Comment comment)
{
Comments.Add(comment);
}
}
public class CustomerComment
{
public long Id { get; set; }
public long CustomerId { get; private set; }
public long CommentId { get; private set; }
}
public class Comment
{
public Comment(long createdByUserId, DateTime createdOnDtm, string content)
{
CreatedByUserId = createdByUserId;
CreatedOnDtm = createdOnDtm;
Content = content;
}
protected Comment()
{}
public long Id { get; private set; }
public long CreatedByUserId { get; init; }
public DateTime CreatedOnDtm { get; init; }
public string Content { get; init; }
public virtual ICollection<Customer> Customers { get; private set; }
//public virtual ICollection<Supplier> Suppliers { get; private set; }
//public virtual ICollection<Other> Others { get; private set; }
}
However when setting up the EF Core model, it seems I have to have the Customers/Suppliers/Others parent entity collections in the Comment domain object
because the relationship looks like a N:M in the relational model even though it is 1:N in the domain model.
This is what the fluent model definition looks like:
modelBuilder.Entity<Customer>()
.HasMany(p => p.Comments)
.WithMany(t => t.Customers)
.UsingEntity<CustomerComment>(
j => j.HasOne<Comment>().WithMany().HasForeignKey(p => p.CommentId),
j => j.HasOne<Customer>().WithMany().HasForeignKey(p => p.CustomerId));
modelBuilder.Entity<Customer>()
.Navigation(p => p.Comments)
.UsePropertyAccessMode(PropertyAccessMode.Property);
modelBuilder.Entity<CustomerComment>()
.HasKey(x => x.Id);
I cannot declare the join type CustomerComment if the Comment does not have the Customers collection. It basically means that my Comment domain object gets 'polluted' with all the possible parent entities
so that EF can understand the link table.
Or is there a way around this?
Is it possible to configure one to one relationship using fluent api on database which does not meet convention requirements?
Below I give you sample of database and generated models.
Be aware of that tables do not define any constraints and indices except primary keys.
Tables:
create table Person (
PersonKey int primary key
)
create table Address (
AddressKey int primary key,
owner int not null // normally should be foreign key to Person
)
Code first models generated from db:
public partial class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonKey { get; set; }
}
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AddressKey { get; set; }
public int Owner { get; set; }
}
To be able to navigate from Address to Person, navigation property was added to Address class:
public partial class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AddressKey { get; set; }
public int Owner { get; set; }
public virtual Person Person { get; set; }
}
If program tries execute this query:
var Addresss = context.Addresss.Include(x => x.Person).ToList();
runtime raises exception: "Invalid column name 'Person_PersonKey'". Because context do not configure any custom mappings it tries to find foreign key by convention but Owner property does not meet convention requirements, hence the exception. So there is a need to add mappings.
If relationship between Person and Address would be one to many we could add such a configuration:
modelBuilder.Entity<Address>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.Owner);
and query defined above would execute correctly. But what if Person class would have navigation property to Address so we would have bidirectional one to one relation:
public partial class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonKey { get; set; }
public virtual Address Address { get; set; }
}
So above configuration will not work and my question is, is it possible to configure it without changing db and property names and if yes what configuration needs to be applied using only fluent api?
Here is my suggested code, I hope I understand you correctly!
public partial class Person
{
public int PersonKey { get; set; }
public Address Address {get;set;}
}
public partial class Address
{
public virtual Person Person { get; set; }
public int PersonId { get; set; }
public string AddressInfo {get;set;}
}
modelBuilder.Entity<Person>()
.HasKey(a => a.PersonKey);
modelBuilder.Entity<Course>()
.Property(c => c.CourseId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Address>()
.HasKey(a => a.PersonId);
modelBuilder.Entity<Person>()
.HasRequired(p => p.Address)
.WithRequiredPrincipal(a => a.PersonId);
I am trying to create one-to-many and reverse one-to-one relationship using code first. Here is what I ma trying to do
1) One-to-Many between two classes and it works as expected.
public class X
{
[Key]
public int XId { get; set; }
public ICollection<Y> Y { get; set; }
}
public class Y
{
[Key]
public int YId { get; set; }
public int XId { get; set; }
public X X { get; set; }
}
public class DataContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Y>()
.HasRequired(y => y.X)
.WithMany(x => x.Y)
.HasForeignKey(y => y.XId);
}
}
Now what I want to do is to create Reverse One-to-One optional relationship between Y and X, such that the X will contain a foreign key of Y...How is it possible? Here is what I am trying to do and it throws some Multiplicity Error
public class X
{
[Key]
public int XId { get; set; }
public ICollection<Y> Y { get; set; }
public int YId {get; set; }
[ForiegnKey("YId")]
public Y YOptional { get; set; }
}
public class Y
{
[Key]
public int YId { get; set; }
public int XId { get; set; }
public X X { get; set; }
public X XOptional {get; set; }
}
public class DataContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Y>()
.HasRequired(y => y.X)
.WithMany(x => x.Y)
.HasForeignKey(y => y.XId);
modelBuilder.Entity<X>()
.HasOptional(x => x.YOptional)
.WithOptionalDependent(y=> y.XOptional);
}
}
You can't have a relationship between two entities that is defined differently from either end. So you can't do 1:* from one direction and 1:1 from another.
Let me make a guess that you don't really want it to be 1:1 from the dependent end. From that end it will always only point to one thing.
In mappings, unlike in life, unless you have many to many, a child only has one parent.
You can, however, create a 0..1 : * relationaship (zero or one to many). Where the parent can have one or more children (e.g. "many") but the child can exist without a parent, but it can never have more than one parent (e.g. "zero or one").
Here is the simplest method of making your classes result in a [zero or one] to many relationship. Notice that I made the foreign key in the class Y a nullable int. WIth this setup, EF conventions will result in a mapping that lets a child exist without a parent.
public class X
{
[Key]
public int XId { get; set; }
public ICollection<Y> Y { get; set; }
}
public class Y
{
[Key]
public int YId { get; set; }
public int? XId { get; set; }
public X X { get; set; }
}
public class DataContext : DbContext
{
public DbSet<X> XSet { get; set; }
public DbSet<Y> YSet { get; set; }
}
Here is a screenshot of visual model derived from the above classes and context.
I think this achieves the behavior you are seeking if my guess that you may just be perceiving it differently is correct.
Using the actual class names you mentioned in the comments:
Mapping a User that can have many Singles is not a problem. However, when you want to map a 1:1 association between a User and a Single you have to choose which of the two is the "principle" entity. You can't have a foreign key column in both tables because one entity will always be inserted before the other one. The "dependent" entity is inserted next, and it refers to the principal's primary key value.
So if User is the principal entity, you could have a class model similar to this:
public class User
{
public User()
{
this.Singles = new HashSet<Single>();
}
public int UserId { get; set; }
public string Name { get; set; }
public Single Single { get; set; }
public virtual ICollection<Single> Singles { get; set; }
}
public class Single
{
public int SingleId { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
public int SuperUserId { get; set; }
public User SuperUser { get; set; }
}
And two options for mappings:
Option 1: User as principal
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(u => u.Singles)
.WithRequired(s => s.SuperUser).HasForeignKey(s => s.SuperUserId);
modelBuilder.Entity<User>().HasOptional(s => s.Single)
.WithOptionalPrincipal(s => s.User).Map(m => m.MapKey("UserId"));
}
In the data model, Single now has two foreign keys, UserId and SuperUserId. This is how to create a User and a Single in User.Single and User.Singles:
var superUser = new User { Name = "superUser1" };
var single = new Single { Name = "single" };
superUser.Singles.Add(single);
db.Users.Add(superUser);
superUser.Single = single;
db.SaveChanges();
And EF will first insert the User, then the Single having both foreign keys equal to the User's primary key.
Option 2: Single as principle
You can also make Single the principal entity in the 1:1 association:
modelBuilder.Entity<User>().HasOptional(s => s.Single)
.WithOptionalDependent(s => s.User).Map(m => m.MapKey("SingleId"));
Now there's only one foreign key in Single (SuperUserId) and a foreign key in User (SingleId). If you execute the same code, now EF will throw
Unable to determine a valid ordering for dependent operations.
This is because there is a chicken-and-egg problem: the Single must be created before the dependent User can be created, but the User must be created before the Single can be added to its Singles collection. This could be solved by assigning the Single later:
var superUser = new User { Name = "superUser1" };
var single = new Single { Name = "single" };
superUser.Singles.Add(single);
db.Users.Add(superUser);
db.SaveChanges();
superUser.Single = single;
db.SaveChanges();
You'd want to wrap this in a TransactionScope, so I think this option is less viable.
Note
As you see, in a 1:1 mapping the foreign key can't be mapped to a property in the class model. There is no HasForeignKey in the fluent API after WithOptionalDependent or WithOptionalPrincipal. Also, this association can only be mapped by the fluent API. In data annotations there is not attribute to indicate the principal end of an association.
I'm new to MVC and EF code first. I'm in struggle to model a real-estate company DB model using EF code-first approach and I did some exercises as well as reading some online tutorials.
First thing I have a customers table that would be in relation with one or more properties he/she has registered as it's owner to sell or to rent, I was wondering if it is possible to have some sub classes inside a model class for registered properties as below:
public Property
{
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public class Apartment{
public int ApartmentID { get; set; }
public int AptSqureMeter { get; set; }
. . .
. . .
}
public class Villa{
public int VillaID { get; set; }
public int VillaSqureMeter { get; set; }
. . .
. . .
}
and also other sub-classes for other types of properties
}
If the answer is Yes, then how should I declare the relations using data annotation or Fluent API, and then please help me how to update both Customers table and Property table with the customer information and property info at the same time?
thanks for your answer in advance.
As #Esteban already provided you with a pretty detailed answer on how to design your POCOs and manage the relationship between them, I will only focus on that part of your question:
how should I declare the relations using data annotation or Fluent API
First of all, you should know that certain model configurations can only be done using the fluent API, here's a non exhaustive list:
The precision of a DateTime property
The precision and scale of numeric properties
A String or Binary property as fixed-length
A String property as non-unicode
The on-delete behavior of relationships
Advanced mapping strategies
That said, I'm not telling you to use Fluent API instead of Data Annotation :-)
As you seem to work on an MVC application, you should keep in mind that Data Annotation attributes will be understood and processed by both by Entity Framework and by MVC for validation purposes. But MVC won't understand the Fluent API configuration!
Both your Villa and Apartment classes have similar properties, if they are the same but as it's type, you could create an enum for that.
public enum PropertyType {
Apartment = 1,
Villa
}
public class Property {
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public PropertyType PropertyType { get; set; }
public int SquareMeter { get; set; }
}
This way of modelating objects is refered as plain old clr object or POCO for short.
Assume this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role {
public int RoleId { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
}
Creating relations with fluent api:
Mapping many to many
On your OnModelCreating method (you'll get this virtual method when deriving from DbContext):
protected override void OnModelCreating(DbModelBuilder builder) {
// Map models/table
builder.Entity<User>().ToTable("Users");
builder.Entity<Role>().ToTable("Roles");
// Map properties/columns
builder.Entity<User>().Property(q => q.UserId).HasColumnName("UserId");
builder.Entity<User>().Property(q => q.Username).HasColumnName("Username");
builder.Entity<Role>().Property(q => q.RoleId).HasColumnName("RoleId");
builder.Entity<Role>().Property(q => q.Name).HasColumnName("Name");
// Map primary keys
builder.Entity<User>().HasKey(q => q.UserId);
builder.Entity<Role>().HasKey(q => q.RoleId);
// Map foreign keys/navigation properties
// in this case is a many to many relationship
modelBuilder.Entity<User>()
.HasMany(q => q.Roles)
.WithMany(q => q.Users)
.Map(
q => {
q.ToTable("UserRoles");
q.MapLeftKey("UserId");
q.MapRightKey("RoleId");
});
Mapping different types of relationships with fluent api:
One to zero or one:
Given this model:
public class MenuItem {
public int MenuItemId { get; set; }
public string Name { get; set; }
public int? ParentMenuItemId { get; set; }
public MenuItem ParentMenuItem { get; set; }
}
And you want to express this relationship, you could do this inside your OnModelCreating method:
builder.Entity<MenuItem>()
.HasOptional(q => q.ParentMenuItem)
.WithMany()
.HasForeignKey(q => q.ParentMenuItemId);
One to many
Given this model:
public class Country {
public int CountryId { get; set; }
public string Name { get; set; }
public virtual List<Province> Provinces { get; set; }
}
public class Province {
public int ProvinceId { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
You now might want to express this almost obvious relationship. You could to as follows:
builder.Entity<Province>()
.HasRequired(q => q.Country)
.WithMany(q => q.Provinces)
.HasForeignKey(q => q.CountryId);
Here are two useful links from MSDN for further info:
Configuring Relationships with the Fluent API.
Code First Relationships Fluent API.
EDIT:
I forgot to mention how to create a many to many relationship with additional properties, in this case EF will NOT handle the creation of the join table.
Given this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
pubilc virtual List<UserEmail> UserEmails { get; set; }
}
pubilc class Email {
public int EmailId { get; set; }
public string Address { get; set; }
public List<UserEmail> UserEmails { get; set; }
}
public class UserEmail {
public int UserId { get; set; }
public int EmailId { get; set; }
public bool IsPrimary { get; set; }
public User User { get; set; }
public Email Email { get; set; }
}
Now that we've added a new property into our join table ef will not handle this new table.
We can achieve this using the fluent api in this case:
builder.Entity<UserEmail>()
.HasKey( q => new {
q.UserId, q.EmailId
});
builder.Entity<UserEmail>()
.HasRequired(q => q.User)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.EmailId);
builder.Entity<UserEmail>()
.HasRequired(q => q.Email)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.UserId);
I have the situation where a User can have several addresses. Accordingly, I have an ICollection on my user class. But I also want the user to be able to choose a default address. So I've done the following:
public class User
{
public int Id { get; set; }
public int? DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public virtual Address DefaultAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
//properties were removed for purpose of this post
}
I would like to remove the public virtual Address DefaultAddress { get; set; } altogether, keep the DefaultAddressId and map it using the Fluent API instead because the current setup is causing a lot of trouble (in this and other classes where I have a similar setup). So can this be done using the fluent api?
UPDATE:
The address class currently doesn't have any reference to the User class, it's a uni-directional relationship. But yes, an address belongs to only ONE user, it's not a many to many relationship. Here's the address class:
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Details { get; set; }
public virtual Area Area { get; set; }
}
I would personally move the Foreign Key relation from User to Address, and add an IsDefaultAddress property on the address class.
public class Address
{
public int Id { get; set; }
// This property marks the FK relation
public virtual User User { get; set; }
public string Name { get; set; }
public string Details { get; set; }
public virtual Area Area { get; set; }
// This property signals whether this is the user's default address
public bool IsDefaultAddress { get; set; }
}
EF will know that it needs a Foreign Key relation between Address and User.
This would simplify your model a great deal. That is, of course, if an address can only belong to one user (as asked by Slauma in the comments).
Your original model in the question should work. You can test it quite easily:
Create new console application (VS 2010)
Name it "EFTestApp"
Add reference to "EntityFramework.dll"
Delete content of Program.cs and copy the following code into the file
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace EFTestApp
{
public class User
{
public int Id { get; set; }
public int? DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public virtual Address DefaultAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var context = new Context())
{
try
{
User user = new User() { Addresses = new List<Address>() };
Address address1 = new Address() { Name = "Address1" };
Address address2 = new Address() { Name = "Address2" };
user.Addresses.Add(address1);
user.Addresses.Add(address2);
context.Users.Add(user);
context.SaveChanges();
// user has now 2 addresses in the DB and no DefaultAddress
user.DefaultAddress = address1;
context.SaveChanges();
// user has now address1 as DefaultAddress
user.DefaultAddress = address2;
context.SaveChanges();
// user has now address2 as DefaultAddress
user.DefaultAddress = null;
context.SaveChanges();
// user has now no DefaultAddress again
}
catch (Exception e)
{
throw;
}
}
}
}
}
In SQL Server Express it creates a new DB called "EFTestApp.Context". You can set breakpoints on every SaveChanges above, step over and watch the changes in the DB.
If you look at the relationships in the database then there are two, and in table Addresses in the DB is a foreign key column User_Id.
I think you could also remove public int? DefaultAddressId { get; set; } and [ForeignKey("DefaultAddressId")]. It creates the same database tables and relationships with an optional DefaultAddress.
Perhaps you want the relationship Address -> User as required (Addresses cannot live alone in the DB without a User). Then you can add this to the Context class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.Addresses)
.WithRequired();
}
It makes User_Id in the Addresses table non nullable and sets up cascading delete by default. So, when a user gets deleted all its addresses get deleted as well.
DefaultAddressId doesn't need any specific mapping because it will be just column in User table without any relation (FK) to Address table. There will be no relation created because navigation property doesn't exist on either side. Also it should be one-to-one relation which will not work because EF doesn't support unique keys.
I like solution provided by #Sergi Papaseit
You don't need to map it if you are removing the DefaultAddress property. You can just have the property there and EF should know how to map it provided DefaultAddressId is in the User table