Entity Framework code first optional ICollection relationship - entity-framework

I new to the code first Entity Framework and trying to solve a optional relationship.
I have the following models
public class Customer
{
public int Id { get; set; }
public int? CardId { get; set; }
public virtual ICollection<Card> Cards { get; set; }
.......
}
public class Card
{
public int Id { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
.......
}
A customer may not have any cards so the relationship needs to be optional, a card must have a customer.
I have tried the following but the collection does not contain the definition for a customer.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Entity<Customer>()
//.HasOptional(lu => lu.Cards)
//.WithRequired(pi => pi.Customer);
}
Can anyone point me in the right direction?
Updated code to reflect suggestions below, unfortunately still no luck.

What it seems like you're trying to do is just create a One-to-Many relationship.
In your Cards model you need to include:
public int CustomerId { get; set; } // Setting up a foreign key.
With this setup, the Customer class will not need:
public int? CardId { get; set; } // not necessary anymore
With a One-to-Many relationship, the ICollection<Card> Cards automatically allows 0 items to be allowed. You do not by default have to build that collection. Think of any collection. You do not have to have any items added to that collection for it to exist. You can also add many items, and take them away later and that's okay.

Related

I am not able to have one-to-many relationship in 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.

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.

Entity Framework POCO - Modelling a 0:1 or 1:1 relationship

Can someone confirm that I have coded the correct relationship between 2 POCO classes so that I have an Order object that can have 0 or 1 CreditCard objects and a CreditCard object that must belong to an Order (some class properties removed for brevity).
public class Order
{
public int OrderId { get; set; }
public string Username { get; set; }
public string Address { get; set; }
public string City { get; set; }
public decimal Total { get; set; }
public CreditCard CreditCard { get; set; }
}
public class CreditCard
{
public int CreditCardId { get; set; }
public int OrderId { get; set; }
public CardType Type { get; set; }
public string Number { get; set; }
public Order Order { get; set; }
}
In my OnModelCreating method I have the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasOptional(or => or.CreditCard).WithRequired(lu => lu.Order);
}
Hopefully the above code is specifying that an Order has an optional CreditCard but the Credit Card requires an Order.
In EF, 1:1 and 1:0..1 (and even 0..1:0..1) relationships require that the primary key is shared between all related entities. Your CreditCardId and OrderId values must be the same.
Your configuration seend to specify the relationship correctly, but the OrderId property in CreditCart is redundant and may cause problems. In CreditCard you may need to mark CreditCardId as not being server-generated too.
If you are using an o/r mapper such as Entity Framework or NHibernate, I would model this as one-to-many and use the public property to limit the collection to one item (actually I don't know if that is possible in EF). Basically, I treat this as a 1:n where n is set by a business rule. I don't love this solution but in my experience that's the best way to model it and I haven't found a better solution in the last almost four years.

code first relationships with multiple foreign keys

I have a scenario I'm getting a little muddled with using EF code first. The classes I've created are below:
public class Company
{
public int Id { get; set; }
public List<Contact> Contacts { get; set; }
public List<Job> Jobs { get; set; }
}
public class Contact
{
public int Id { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int CompanyId { get; set; }
public List<Job> Jobs { get; set; }
}
public class Job
{
public int Id { get; set; }
[ForeignKey("CompanyContactId")]
public virtual CompanyContact CompanyContact { get; set; }
public int CompanyContactId { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int CompanyId { get; set; }
}
However, when I build the DB I get the following error:
Introducing FOREIGN KEY constraint 'FK_Contacts_Company_CompanyId' on table 'Contacts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
So a little research indicates the answer to this is to use the Fluent API to define the mappings as required but I can't get my head around how to do this or find an example of a similar scenario.
I realise I could remove the Company class from Job and navigate through Contact but I'd prefer not to if possible.
Any help gratefully received
You want to use the EF model builder to set up these relationships.
An example of how you would do this for one of your properties would be the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasOptional(e => e.Company).WithMany(c=>c.Contacts);
}
For more of an explanation around how to use the modelbuilder take a look at my article on EF Navigation Properties

How Do I Resolve "A specified Include path is not valid"?

I have a parent-child relationship setup that is fairly basic. The end result is that I want to be able to return the resulting tables as JSON through ASP.NET MVC WebAPI. I am using Entity Framework 5.0 beta 2.
I can demonstrate the error I'm running into with a simple example. Given the classes Category and Product with the corresponding data context:
public class Category
{
public int CategoryId { get; set; }
public string Title { get; set; }
public virtual IEnumerable<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Title { get; set; }
public virtual Category Category { get; set; }
public virtual int CategoryId { get; set; }
}
public class ProductDataContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
When I try to run a query that includes the products I get the following error:
A specified Include path is not valid. The EntityType 'FooAndBar.Category'
does not declare a navigation property with the name 'Products'.
The statement to fetch is pretty straightforward:
var everything = dc.Categories
.Include(c => c.Products);
What is the correct way to setup the columns and/or the query so that the Products are included with the Categories?
Child collection properties must be declared as anICollection<T>, not anIEnumerable<T>.
Also, you do not need to explicitly add a CategoryId field to the child class; EF will create that automatically in the database.