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
Related
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.
So I need to have User, Companies and Channels.
User:
is tied to one Company
Channel:
is tied to one Company
can have many users
Company:
can have many users
can have many channels
public class User {
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<Channel> Channels { get; set; }
}
public class Company {
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Channel> Channels { get; set; }
}
public class Channel {
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual Company Company { get; set; }
}
public class CompanyUser {
public long Id { get; set; }
public long CompanyId { get; set; }
public virtual Company Company { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public bool IsOwner { get; set; }
public bool ChannelLimit { get; set; }
}
public class ChannelUser {
public long Id { get; set; }
public long ChannelId { get; set; }
public virtual Channel Channel { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public bool IsOwner { get; set; }
}
How to make this efficient and working? What's the way to handle all relations?
Thanks
You have defined a number of many-to-many relationships where 1-to-many would suffice:
User is tied to one Company
Channel is tied to one Company, can have many users
Company can have many users, can have many channels
public class User
{
[Key]
public int UserId { get; set; }
public virtual Channel Channel { get; set; }
}
public class Channel
{
[Key]
public int ChannelId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<User> Users { get; set; } = new List<User>();
}
public class Company
{
[Key]
public int CompanyId { get; set; }
public virtual ICollection<Channel> Channels { get; set; } = new List<Channel>();
}
When mapped, the company mapping will have a HasMany(x => x.Channels).WithRequired(x => x.Company);
The channel mapping will have a HasMany(x => x.Users).WithOptional();
To get Users for a company you can go through Channels:
var companyUsers = context.Companies
.Where(c => c.CompanyId == companyId)
.SelectMany(c => c.Channels.SelectMany(ch => ch.Users))
.ToList();
That being a very basic example to get all users associated with a single company. When reading company and related channel/user info you can dive through the association to select and filter the details as desired.
Just as with DB design, it is usually advisable to keep entity relationships relatively normalized to avoid references to each other scattered everywhere. EF does a great job of building queries to pull data through the navigation properties where needed.
Edit: Ok, users can create a channel, potentially creating multiple channels and assuming from the earlier description multiple other users can be assigned to the channel. Channels are also potentially "owned" by one company.
Take 2:
public class User
{
[Key]
public int UserId { get; internal set; }
public virtual Company Company { get; internal set; } // optional
public virtual ICollection<ChannelUser> ChannelUsers { get; internal set; } = new List<ChannelUser>();
[NotMapped]
public ICollection<Channel> Channels
{
get { return ChannelUsers.Select(x => x.Channel).ToList(); }
}
[NotMapped]
public ICollection<Channel> OwnedChannels
{
get { return ChannelUsers.Where(x => x.IsOwned).Select(x => x.Channel).ToList(); }
}
}
public class Channel
{
[Key]
public int ChannelId { get; internal set; }
public virtual ICollection<ChannelUser> ChannelUsers { get; internal set; } = new List<ChannelUser>();
public virtual ICollection<ChannelCompany> ChannelCompanies { get; internal set; } = new List<ChannelCompany>();
[NotMapped]
public User OwnerUser
{
get { return ChannelUsers.SingleOrDefault(x => x.IsOwner)?.User; }
}
[NotMapped]
public User OwnerCompany
{
get { return ChannelCompanies.SingleOrDefault(x => x.IsOwner)?.Company; }
}
}
public class Company
{
[Key]
public int CompanyId { get; internal set; }
}
public class ChannelUser
{
public int ChannelId { get; internal set; }
public int UserId { get; internal set; }
public bool IsOwner { get; internal set; }
public virtual Channel Channel { get; internal set; }
public virtual User User { get; internal set; }
}
public class ChannelCompany
{
public int ChannelId { get; internal set; }
public int CompanyId { get; internal set; }
public bool IsOwner { get; internal set; }
public virtual Channel Channel { get; internal set; }
public virtual Company Company { get; internal set; }
}
This maps similar to your original with many-to-many relationships between Channel and User and Company. The concept of an "owner" would be on the joining tables. I added some unmapped helper properties to make it easier for code using the entities to navigate through the linking entities. Be warned though that these properties cannot be used in EF Linq expressions. For instance when writing Linq queries against the dbSets and navigation properties you will need to use the linking entity collections because EF won't be able to resolve the unmapped ones. EF6 & EF Core 3+ will throw exceptions if you try to use them in a query, EF Core 2 would generate a warning and auto-materialize the query into memory which is a potentially huge performance/memory sink.
The trouble with models like this is that they can get the job done, but will rely on your code logic to enforce that the rules around expected relationships between channels, users, and companies are enforced. For instance, expecting that every channel has 1, and only 1 owning user (if required) and 0 or 1 owning company. (if optional)
To do this, I would recommend using Actions (methods) on the entities to modify state rather than accessing setters/collections directly. For example, if you can create a channel for a user, have a CreateChannel() method on user with the required parameters:
public Channel CreateChannel(string name /* , other required fields */)
{
var channel = new Channel
{
Name = name,
//...
};
var channelUser new ChannelUser { Channel = channel, User = this, IsOwner = true });
ChannelUsers.Add(channelUser);
channel.ChannelUsers.Add(channelUser);
return channel;
}
This assumes that the DbContext that read the associated User entity is in-scope and alive when user.CreateChannel() is called, and SaveChanges() is called afterwards. For instance, somewhere in a controller action to create a Channel from data provided in the Post:
using (var context = new AppContext())
{
var user = context.Users.Single(x => x.UserId == userId);
var channel = user.CreateChannel(channelName /*, .... */);
context.SaveChanges();
// Potentially return view information for the newly created Channel...
}
Changing a channel owner between users (whether limited to user already associated to the channel or a new user) can be contained in a method on Channel to update the appropriate collections in one operation to ensure the IsOwner state is kept valid. This is where I will make setters internal to discourage attempting to mutate state with setters instead of using action methods.
Anyhow, this should hopefully give you a few things to think about. Ultimately look to narrow down the allowed relationships to the minimum viable connection points between the entities. Many-to-many relationships can complicate things in terms of enforcing valid combinations.
I have a little problem when I try to save an item in my DB using EntityFramework.
My classes looks like:
public partial class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string LongName { get; set; }
public string Adress { get; set; }
public City City { get; set; }
public Country Country { get; set; }
public string VATNumber { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
And when I try to create a new site in my controller it works, but when I try to add a link to an existing Country :
if (SiteProvider.GetSiteByName(Site.Name) == null)
{
Site.Country = CountryProvider.GetCountryById(1);//it's working, i see the link to the right country
SiteProvider.Create(Site);
}
public static void Create(Site Site)
{
using (MyDBContext Context = new MyDBContext())
{
Context.Site.Add(Site);
Context.SaveChanges(); //here is the problem
}
}
I got this error:
SqlException: Cannot insert explicit value for identity column in
table 'Country' when IDENTITY_INSERT is set to OFF
Thanks in advance for your help.
Add CountryId property to Site class and when adding a new Site set CountryId instead of Country property
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public Country Country{ get; set; }
You have a slight issue with your use of contexts here. You have used one DBContext instance to load the country (where this country object will be tracked) and then a second DBContext to save the site (where the first country object is a property).
It is preferable to perform all your operations for a single unit of work by using one DB context (that would be shared between your classes) and the responsibility for disposing of it to be handled outside your repository layer.
I'm creating a simple application for university where a student can make some type of request which is then processed by an employee of particular speciality.
I would like to use default MVC5 identity system and to extend the ApplicationUser class using TPH pattern. So I added the common properties to the ApplicationUser:
public class ApplicationUser : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
}
then I created two classes which inherits the ApplicationUser:
public class Student : ApplicationUser
{
public string PersonalNumber { get; set; }
public bool Monitor { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
public class Employee : ApplicationUser
{
public virtual EmployeeSpeciality EmployeeSpeciality { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
what I currently want is to make both types of users register as a base Identity and to keep both in a single table as in the inheritance example on asp.net
As I thought, it would be enough to initialize user var in AccountController which is then passes to the UserManager as a Student or as an Employee. But after trying to register as a Student i'm getting this exception:
Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'PersonalNumber'.
Invalid column name 'Monitor'.
Invalid column name 'EmployeeSpeciality_Id'.
Invalid column name 'Group_Id'.
My context class:
public class EntityContext : IdentityDbContext
{
public EntityContext()
: base("DbConnection")
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Employee> Employees { get; set; }
...
}
and a part of controller's action:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new Student()
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
I tried setting ApplicationClass to an abstract class, but no luck. Any help would be appreciated.
UPDATE: The problem wasn't in the code itself. I simply haven't dropped (or updated) the database after making these changes to the model. After it everything works just fine.
#Dragonheart: I tried this repro and it would work fine if you remove the DBSet declarations in you context class. The IdentityDbContext would handle you TPH pattern and add a Discriminator column in the table to differentiate the child classes.
As ApplicationUser is inherited from IdentityUser, remove it from your DbContext class. On the other hand, there is no need to create an abstract class (you can create if you prevent from creating a user except from Student or Employee classes. For more information you might have a look at Table-per-Hierarchy.
For Register part, try something like that:
Student user = new Student
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = UserManager.Create(user, model.Password);
Hope this helps...
I have a problem with the Entity Framework.
public class User : Receiver
{
public User()
{
if (Groups == null)
Groups = new List<Group>();
if (Buddies == null)
Buddies = new List<User>();
}
[Required]
public string PhoneNumber { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<Group> Groups { get; set; }
}
public class Receiver
{
public Receiver()
{
Guid = Guid.NewGuid();
Created = DateTime.Now;
}
[Key]
public Guid Guid { get; set; }
[Required]
public DateTime Created { get; set; }
}
When i try to add a user...
User user = new User
{
Guid = new Guid("8cd094c9-e4df-494e-b991-5cf5cc03d6e3"),
PhoneNumber = "+4991276460"
};
cmc.Receivers.Add(user);
... it ends in follogwing error.
The object of the Type "System.Collections.Generic.List`1[Project.Models.User]" can't be converted to "Project.Models.User".
When i comment out following two lines:
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...the programm runs fine.
I hope someone can help me to fix this problem.
Otherwise it runs into an error at this line : cmc.Receivers.Add(user);
In your mapping...
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...you specify that User.Buddies is part of a one-to-many relationship and that User.Guid (=Receiver.Guid) is the foreign key in this relationship. But User.Guid is also the primary key, hence it must be unique. As a result a User cannot have a list of Buddies but only a single reference.
The mapping makes no sense but the exception is not very helpful and difficult to understand. (Somehow EF seems to recognize internally that the Buddies cannot be a list with that mapping and wants to cast the list to a single reference. It should detect in my opinion that the mapping is invalid in the first place.)
For a correct one-to-many mapping you need a foreign key that is different from the primary key. You can achieve that by either removing the [ForeignKey] annotation altogether...
[JsonIgnore]
public IList<User> Buddies { get; set; }
...in which case EF will create a default foreign key in the Receivers table (it will be some column with an underscore in its name, but you can rename that with Fluent API if you don't like the default name) or by adding your own foreign key property to the User class:
public Guid? BuddyGuid { get; set; }
[ForeignKey("BuddyGuid"), JsonIgnore]
public IList<User> Buddies { get; set; }