I am not able to have one-to-many relationship in Entity Framework - entity-framework

I am following examples from the internet but it's not working. The database is getting created successfully, there is no error.
What I want to have is: one user can have multiple transactions, and a transaction can have references to two users. One of those is the user who did the transaction, the second is the user to whom transaction is done.
But what is happening is I am getting three foreign keys in the Users table, but none in the Transactions table.
See image below:
My classes
public class User
{
public int userId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CardNumber { get; set; }
public string Password { get; set; }
public int Balance { get; set; }
public string UserType { get; set; }
public string ProfileUrl { get; set; }
public IList<Transaction> Transactions { get; set; }
}
public class Transaction
{
public Transaction()
{
this.TranscationDateTime = DateTime.UtcNow;
}
public int TransactionId { get; set; }
public int Amount { get; set; }
public User FromUser { get; set; }
public User ToUser { get; set; }
public DateTime TranscationDateTime { get; set; }
}
public class DB: DbContext
{
public DB() : base("name=DBConnection")
{ }
public DbSet<User> Users { get; set; }
public DbSet<Transaction> Transactions { get; set; }
}

You need to make some modification to your code.
First of all, each navigation property needs to be marked as virtual, in order to allow Entity Framework to lazy loading, unless you want always eager load all your navigations (could be a choice, is up to you).
After that, each of your user has outgoing and incoming transactions, so for the User class:
public class User
{
public int userId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CardNumber { get; set; }
public string Password { get; set; }
public int Balance { get; set; }
public string UserType { get; set; }
public string ProfileUrl { get; set; }
public virtual IList<Transaction> IncomingTransactions { get; set; }
public virtual IList<Transaction> OutgoingTransactions { get; set; }
}
Let's make virtual navigation properties of Transaction class
public class Transaction
{
public Transaction()
{
this.TranscationDateTime = DateTime.UtcNow;
}
public int TransactionId { get; set; }
public int Amount { get; set; }
public virtual User FromUser { get; set; }
public virtual User ToUser { get; set; }
public DateTime TranscationDateTime { get; set; }
}
Last, but not least, let's inform your DbContext of how things are supposed to go:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString) { }
public DbSet<User> Users { get; set; }
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Transaction>()
.HasRequired<User>(t => t.FromUser)
.WithMany(u => u.OutgoingTransactions).WillCascadeOnDelete(false);
builder.Entity<Transaction>()
.HasRequired<User>(t => t.ToUser)
.WithMany(u => u.IncomingTransactions).WillCascadeOnDelete(false);
}
}
This should be enough for EF autodiscovery to make the right assumptions and create right database structure, that would be two FKs in Transaction table each of them to the primary key of Users table.
And voilĂ :

This happens because EF doesn't know that one of the FromUser and ToUser fields is supposed to match the collection Transactions - since you are not following the naming conventions. You have several options on how to resolve this situation:
If you only want to match Transactions collection with either FromUser or ToUser but not both, you can use [ForeignKey] and/or [InverseProperty] attributes to setup the database relation explicitly
If you want to use BOTH of them, then you would need to specify two collections in the User class - e.g. TransactionsFromUser and TransactionsToUser. You might still need to setup the relationships explicitly through the attributes though

What i want to have is one user can have multiple transaction but a transaction can have reference to two user.
Your current database model reflects this accuratly. I will explain why in the rest of my answer.
The User table can not hold the foreign keys to the Transactions table because one User can be associated with multiple Transactions. If the FK column was on the User table, it would need to hold more than one TransactionId.
Instead, the references to the Users are stored in the Transaction table. So every Transaction only has to store a single UserId per FK column.
Transaction.User_userId tells us that this Transaction is in the IList<Transaction> Transactions of the User with the stored User_userId.
To get this list of Transactions for a certain user, we query
SELECT *
FROM Transactions t
INNER JOIN Users u on t.User_userId = u.userId
WHERE u.userId = {theUserId}
The additional FKs ToUser_userId and FromUser_userId exists because they might reference different Users.
If the semantics of the IList<Transaction> Transactions is actually "all transactions that originated from this User", you could configure the ModelBuilder to use the FromUser_userId FK for this collection instead of creating the third FK User_userId. See the answer of Sergey.

Related

EF Code First relations and associative entity

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.

Foreign key with OR logic

I have CodeFirst design like this:
public class Email
{
public string Address { get; set; }
public Company Company { get; set; }
public int? CompanyId { get; set; }
public User User { get; set; }
public int? UserId { get; set; }
//many other props
}
public class Company
{
public List<Email> Emails { get; set; }
}
public class User
{
public List<Email> Emails { get; set; }
}
In a good way, Email can belong to only one foreign key: CompanyId or UserId. But now it allows CompanyId and UserId. It's wrong. Anyway, that design with nullables is ugly. For example, to get all emails linked to companies I need do this:
var companyEmails = _context.Emails.Where(x => x.CompanyId.HasValue);
I feel there is a better approach to define multiply foreign keys with OR logic. Please, help me find a way.
If you want to have the only one reference to Company or User, that mean it will not be FK and also you should have additional field with description, to which table this field points.
Alternatively, you can try Table per Hierarchy approach. At this case database table will remain almost the same, only new Discriminator column will be implicitly added, to distinguish classes, but you will can to write more "elegant" code:
public abstract class Email
{
public string Address { get; set; }
}
public class CompanyEmail : Email
{
public Company Company { get; set; }
public int? CompanyId { get; set; }
}
public class UserEmail : BaseEmail
{
public User User { get; set; }
public int? UserId { get; set; }
}
Usage:
var companyEmails = _context.Emails.OfType<CompanyEmail>();
//underlying query:
//select * from dbo.Emails where Discriminator = 'CompanyEmail'

EF Code First Error

I am trying to create some tables using Code First. Here is my code:
public class Country
{
[Key]
public int Id { get; set; }
public string CountryName { get; set; }
}
public class State
{
[Key]
public int Id { get; set; }
public string StateName { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public int StateId { get; set; }
public virtual Country Country { get; set; }
public virtual State State { get; set; }
}
public class ProductContext : DbContext
{
public DbSet<Country> Country { get; set; }
public DbSet<Customer> Customer { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
When I Execute this code the following error occurs:
Introducing FOREIGN KEY constraint
'FK_dbo.State_dbo.Country_CountryId' on table 'State' may cause cycles
or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO
ACTION, or modify other FOREIGN KEY constraints. Could not create
constraint. See previous errors.
But I want the CountryId in State Table to be a foreign key. What am I missing here? Can anybody guide me the correct way of achieving this?
Entity Framework is worried about deletion here- because the User has a direct relationship to a Country and also a State and the State also relates to a Country you effectively have a potential loop of User -> State -> Country -> User which would mean that if cascade deletions were enabled the moment you deleted one user you would potentially delete everything in your database.
The answer is in the error message- by disabling cascade deletions across some of these relationships ( which is logical - deleting a user doesn't mean you want to delete their state and country ) you will avoid this risk. As you might imagine this has come up on SO before.
As an aside, having the Country on the User and also on the State looks like questionable denormalisation - there may be a good reason for it, but that happens less often than you would expect.

Advanced TPH Mapping to Legacy Database

I have been working on a project in which I am trying to mold entity framework to an existing FoxPro 2.x database in order to use the data while leaving the tables readable to a legacy application (more details on my previous question).
I've had pretty good luck configuring the DBContext to the physical data tables and I have most of my mapping set up. The legacy data structure has a Bills table with a unique primary Id key, but all the LineItems that can be posted to a bill are stored in a single Charges table without a simple primary key.
My question pertains to discriminator mapping in code-first EF. I am recreating the table as TPH in my data objects, so I have
public abstract class Posting
{
public System.DateTime? Post_Date { get; set; }
public string Bill_Num { get; set; }
public string Type { get; set; }
public string Pcode { get; set; }
public string Pdesc { get; set; }
public decimal? Custid { get; set; }
public string Createby { get; set; }
public System.DateTime? Createdt { get; set; }
public string Createtm { get; set; }
public string Modifyby { get; set; }
public System.DateTime? Modifydt { get; set; }
public string Modifytm { get; set; }
public string Linenote { get; set; }
public decimal? Version { get; set; }
public string Id { get; set; }
public string Batch { get; set; }
public virtual Billing Bill { get; set; }
}
public abstract class Charge : Posting
{
}
public class ServiceLine : Charge
{
public string Chargeid { get; set; }
public virtual ICollection<Payment> Payments { get; set; }
}
public class ChargeVoid : Charge
{
}
public abstract class Payment : Posting
{
}
public class PaymentLine : Payment
{
public string Postid { get; set; }
public string Svc_Code { get; set; }
public string Name { get; set; }
public string Checkno { get; set; }
public System.DateTime? Checkdate { get; set; }
}
public class PaymentVoid : Payment
{
}
where my mapping strategy so far is along these lines:
public class PostingMap : EntityTypeConfiguration<Posting>
{
public PostingMap()
{
// Primary Key
this.HasKey(t => new {t.Bill_Num, t.Post_Date, t.Pcode});
this.Map<Charge>(m => m.Requires("Type").HasValue("C"))
.ToTable("Charges");
this.Map<Payment>(m => m.Requires("Type").HasValue("P"))
.ToTable("Charges");
}
}
I have omitted some fields and mapping classes, but this is the core of it.
Every record has the C/P classification, so this makes everything in the table either a Charge or a Payment.
Every Posting is associated with a Bill via Bill_Num foreign key.
The ServiceLine object is only distinct from ChargeVoid objects (which are adjustment entries and no-value information entries associated with a bill) by having values for Pcode and Chargeid (which is just Bill_Num tagged with 01++). I have no idea how to model this.
It is very similar for the Payment hierarchy as well.
So with my current setup, I have Postings which doesn't have a unique key, Charges which has a subset of ServiceLines with values for Chargeid and Pcode and a subset with nulls, and Payments similar to Charges. PaymentLines are also many-to-one with ServiceLines by way of Pcode while PaymentVoids have Pcode = null.
Is there a way I can assign this complex mapping since I can't simply discriminate on !null? On top of that, will EF handle the key assignments once I get the inheritance set up, or am I going to have issues there as well?
Also, if there is a better way to break this object inheritance down, I am all ears.

Database Generated by EF5 Not Creating Join Table When There are Multiple Relationships

I have a User and an Organization class. They look like this
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Organization> Organizations { get; set; }
}
public class Organization : EntityBase
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
And both inherit from an EntityBase class to get common fields like Id and created/updated tracking.
public abstract class EntityBase
{
public int Id { get; set; }
public DateTime Created { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime Updated { get; set; }
public virtual User UpdatedBy { get; set; }
}
As denoted by the ICollection properties on both, there should be a many-to-many relation. However when my database is autogenerated I get incorrect foreign keys added to my tables
If I change the CreatedBy and UpdatedBy to be strings instead of User properties I get a join table, which is what I was looking for.
Is this a matter of Entity Framework simply being confused and I need to supply many-to-many configuration in the using fluent mappings, or have I done something wrong?
If you have multiple relationships you need to configure them manually by fluent API or using attributes,
Note:If you have multiple relationships between the same types (for
example, suppose you define the Person and Book classes, where the
Person class contains the ReviewedBooks and AuthoredBooks navigation
properties and the Book class contains the Author and Reviewer
navigation properties) you need to manually configure the
relationships by using Data Annotations or the fluent API
Here is the article from Microsoft.