I am using the Linq-to-Sqlite ORM using the Entity Framework. Since Linq-to-Sqlite cannot itself create tables based on a model using code first approach, I am forced to reuse my existing table schema to build the entity model design. Below is my expected model design:
public class Movie
{
public int MovieId { get; set; }
public string Title { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
public class Genre
{
public int GenreId { get; set; }
public string GenreName { get; set; }
}
Below is existing table schema:
CREATE TABLE Movie
(
MovieId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
Title TEXT
)
CREATE TABLE "Genre"
(
'GenreId' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
'GenreName' TEXT NOT NULL,
UNIQUE(GenreName)
)
CREATE TABLE MovieGenre
(
'MovieId' INTEGER NOT NULL REFERENCES Movie(MovieId),
'GenreId' INTEGER NOT NULL REFERENCES Genre(GenreId),
UNIQUE(MovieID, GenreID)
)
So, basically a one movie can have many Genre's assigned to it. However, I would want to have any relationship from the Genre entity to the Movie entity.
Below is my overridden OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
try
{
modelBuilder.Entity<Movie>()
.HasMany<Genre>(m => m.Genres)
.WithOptional()
.Map(mg =>
{
mg.ToTable("MovieGenre");
mg.MapKey("GenreId");
});
}
catch (Exception ex)
{
throw ex;
}
}
I am getting an exception Message=The specified table 'MovieGenre' was not found in the model. Ensure that the table name has been correctly specified.
How can we map the relationship table MovieGenre to the Movie and Genre entity so that I can populate the Movie.Genres ICollection property ?
That kind of relationship is many-to-many and not one-to-many. I'm not an expert in SQL Lite but I think the last table should be like this (see this link):
CREATE TABLE "MovieGenres"
(
'MovieId' INTEGER NOT NULL,
'GenreId' INTEGER NOT NULL,
PRIMARY KEY (MovieId, GenreID),
FOREIGN KEY (MovieId) REFERENCES Movies(MovieId),
FOREIGN KEY (GenreId) REFERENCES Genres(GenreId)
)
To map that relationship in EF, the configuration would be this way:
modelBuilder.Entity<Movie>().HasMany(m => m.Genres)
.WithMany(g =>g.Movies)
.Map(c =>{ c.MapLeftKey("MovieId");
c.MapRightKey("GenreId");
c.ToTable("MovieGenres");
});
But first add the Movies collection property in the Genre entity:
public class Genre
{
//...
public virtual ICollection<Movie> Movies{ get; set; }
}
Related
So i wanted to apply a relation of 1 to 1 from one table to another, with navigational properties on each one and a foreign key that is accessable on at least one of the models.
Lets suppose this example
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
modelBuilder.Entity<User>().HasOptional<Contact>(u=> u.Contact)
.WithRequired(c => c.User).Map(m => m.MapKey("ContactId")).
Similar to the same example used in this stack overflow question:
EF Code First - 1-to-1 Optional Relationship
The problem is that it gives an error saying that the Property name 'ContactId' is already defined.
But i want to have this foreign property defined both at the database and on the model, so that i can use for example linq:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.ContactId == contactId);
or is this acceptable or very inneficient:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.Contact.Id == contactId);
This last options will create a join between the two tables while query the database, right?
The downside of the correct model (i.e. without explicit User.ContactId property) is that in reality it's still a 1:n relationship. The database doesn't enforce 1:1. It's just a FK. The only way to make a true, database-enforced 1:1 association in EF6 is one in which the dependent entity (here: User) has a primary key that's also a foreign key to the principal entity (Contact):
public class User
{
public int Id { get; set; }
public string Username { get; set; }
//public int ContactId { get; set; } <= removed
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
And:
modelBuilder.Entity<User>()
.HasRequired<Contact>(u => u.Contact)
.WithOptional(c => c.User);
This generates the following database schema:
CREATE TABLE [dbo].[Users] (
[Id] [int] NOT NULL,
[Username] [nvarchar](max),
CONSTRAINT [PK_dbo.Users] PRIMARY KEY ([Id])
)
CREATE TABLE [dbo].[Contacts] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Contacts] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_Id] ON [dbo].[Users]([Id])
ALTER TABLE [dbo].[Users] ADD CONSTRAINT [FK_dbo.Users_dbo.Contacts_Id]
FOREIGN KEY ([Id]) REFERENCES [dbo].[Contacts] ([ID])
As for querying, in a query like context.Users.Where(u => u.Contact.ID == 4), EF6 will notice that no Contact fields are requested and it will short-circuit the FK to User.Id, i.e. no join. But of course, in this setup, you may as well use context.Users.Where(u => u.Id == 4).
In EF core it would be possible to use your model, with User.ContactId, by this mapping:
modelBuilder.Entity<User>()
.HasOne(u => u.Contact)
.WithOne(c => c.User)
.HasForeignKey<User>(u => u.ContactId);
EF core is smart enough to create a unique index on User.ContactId, so this is a database-enforced 1:1 association with a separate FK.
My problem: inserting an entity with an owned property fails.
I have a Restaurant entity with an Address owned property. When I try to create an new entity and insert into the database, an exception is thrown at SaveChanges:
Cannot insert the value NULL into column 'RestaurantId', table 'AppRefDB.dbo.Addresses'; column does not allow nulls. INSERT fails.
What I did
My table Address looks like this:
CREATE TABLE [dbo].[Addresses]
(
[RestaurantId] INT NOT NULL,
[Number] NVARCHAR(8) NULL,
[Street] NVARCHAR(150) NOT NULL,
[Zip] NVARCHAR(10) NOT NULL,
[Town] NVARCHAR(50) NOT NULL,
[Site] NVARCHAR(150) NULL ,
CONSTRAINT [PK_Addresses]
PRIMARY KEY ([RestaurantId]),
CONSTRAINT [FK_Address_Restaurants_RestaurantId]
FOREIGN KEY ([RestaurantId]) REFERENCES [Restaurants] ([Id])
ON DELETE CASCADE
)
where RestaurantId is the primary key and FK from Restaurant table.
And
CREATE TABLE [dbo].[Restaurants]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[Name] NVARCHAR(50) NOT NULL,
CONSTRAINT [FK_Restaurants_TCategories]
FOREIGN KEY ([IdCategory]) REFERENCES [Categories]([Id])
)
I defined my property like this in OnModelCreating:
modelBuilder.Entity<Restaurant>()
.OwnsOne(p => p.Address)
.ToTable("Addresses");
And I save like this:
await _dbContext.Set<Restaurant>()
.AddAsync(restaurant, cancellationToken);
_dbContext.SaveChangesAsync();
What I am looking for
What should I change in order to EF understand RestaurantId should get the newly created Id from Restaurant table before inserting the Address?
I am using EF Core 3.
Update works fine, I just have a problem with creating an new restaurant/address
EDIT: my model
public class Restaurant
{
[Key]
public int Id { get; set; }
[Required, StringLength(50)]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[Required, StringLength(150)]
public string Street { get; set; }
[StringLength(8)]
public string Number { get; set; }
[Required, StringLength(10)]
public string Zip { get; set; }
[Required, StringLength(50)]
public string Town { get; set; }
[StringLength(150)]
public string Site { get; set; }
}
Edit2 :
I tested a synchronous version as well
In this case, you have a Class object which has a collection of addresses.
using (var context = new YourContext())
{
var model= new Restaurant{ Name = "McDonald's" };
model.addresses.Add(new addresses{ street="test",.... });
model.addresses.Add(new addresses{ street="test",.... });
context.Restaurant.Add(model);
context.SaveChanges();
}
this would solve your problem.
You can add in both your classes
public ICollection<YourEntity> YourEntity{ get; set; }
or you can use foreign keys.
[ForeignKey("Restaurant")]
public long RestaurantId { get; set; }
public Restaurant Restaurant{ get; set; }
if you add this in your address entity you need to first create one resturant then add addresses separately
In fact this is probably a bug in EF 3.0
I tested with EF 3.1 (preview) and it is working fine
In EF6 we have two ways to declare relationship between two tables:
Annotations Attributes
Fluent API
Today (by accident) I removed one relationship between two tables and everything kept working well. I was very surprised because there was no way EF would know how those two tables are connected.
Tables look like that:
[Table("item")]
public class Item
{
public Item()
{
ItemNotes = new HashSet<ItemNote>();
}
[Key]
[Column("itemID", TypeName = "int")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int itemID { get; set; }
public ICollection<ItemNote> ItemNotes { get; set; }
}
[Table("itemNotes")]
public class ItemNote
{
[Key]
[Column("id", TypeName = "int")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("itemID", TypeName = "int")]
public int ItemId { get; set; }
[Column("note", TypeName = "varchar")]
[MaxLength(255)]
public string Note { get; set; }
}
Fluent API:
public class MyContext : DbContext
{
public MyContext()
: base("name=MyContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MyContext>(null);
//I removed this relationship:
//modelBuilder.Entity<Item>().HasMany(i => i.ItemNotes).WithRequired().HasForeignKey(i => i.ItemId);
base.OnModelCreating(modelBuilder);
}
}
Here is the test I made: It's an integration test, that connects to the real database, gets items with notes and tests the EF:
[TestCase]
public void QueryItemWithItemNotesTest()
{
FniContext fniContext = new FniContext();
int itemId = fniContext.Database.SqlQuery<int>("SELECT TOP(1) itemId FROM item WHERE itemId IN (SELECT itemId FROM dbo.itemNotes)").FirstOrDefault();
var item = fniContext.Items.AsNoTracking()
.Include(i => i.ItemNotes)
.Where(i => i.itemID == itemId).FirstOrDefault();
Assert.IsNotNull(item);
Assert.Greater(item.ItemNotes.Count, 0);
}
It passes! It loads all notes! How come?!
I kept investigating and it turned out that in case of 1:many relationship I totally don't have to have any relationship in the code. The only time I need it is with 1:1 relationship. Am I'm missing something? Most of relationships are 1:many, so does it mean Fluent API is used for 1:1 most of the time?
Entity Framework has some conventions that you do not need to define explicitly.
From https://msdn.microsoft.com/en-us/library/jj679962(v=vs.113).aspx#Anchor_2
In addition to navigation properties, we recommend that you include
foreign key properties on the types that represent dependent objects.
Any property with the same data type as the principal primary key
property and with a name that follows one of the following formats
represents a foreign key for the relationship: '<navigation property
name><principal primary key property name>', '<principal class
name><primary key property name>', or '<principal primary key property
name>'. If multiple matches are found then precedence is given in the
order listed above. Foreign key detection is not case sensitive. When
a foreign key property is detected, Code First infers the multiplicity
of the relationship based on the nullability of the foreign key. If
the property is nullable then the relationship is registered as
optional; otherwise the relationship is registered as required.
I am starting a code first EF MVC project. Below is the code for a Message model that I am creating. Is it possible to create a tags property that is a list of tags (another model I created) like I am attempting below?
public class Message
{
public int Id { get; set; }
public string Text { get; set; }
public byte[] Attachment { get; set; }
[Required]
public MessageBoard MessageBoard { get; set; }
[Required]
public virtual List<Tag> Tags { get; set; }
}
After attempting the update-database -verbose command, I see that it does not add a Tags class to my database. The console shows this db command for messages:
CREATE TABLE [dbo].[Messages] (
[Id] [int] NOT NULL IDENTITY,
[Text] [nvarchar](max),
[Attachment] [varbinary](max),
[MessageBoard_Id] [int] NOT NULL,
CONSTRAINT [PK_dbo.Messages] PRIMARY KEY ([Id])
)
How can I create this foreign key relationship between messages and tags?
I assume that you one Many to Many relationship to reuse existing tags.
First of all you have to add to your Tag class a reference to Message
public virtual List<Message> Messages { get; set; };
Then in your model configuration class you have to set the relation many to many, with the following code :
modelBuilder.Entity<Message>()
.HasMany<Tag>(m => m.Tags)
.WithMany(t => t.Messages)
.Map(mt =>
{
mt.MapLeftKey("MessageID");
mt.MapRightKey("TagId");
mt.ToTable("MessagesTag"); //Name of table many to many
});
And don't forget to add class Tag in your DBContext too.
public DbSet<Tag> Tag { get; set; }
I have a database with this structure
TASKs SubTasks
=============================
Id (pk) Id (pk)
Name Name
TaskCode ParentTaskCode
Now I need to connect SubTasks Table to Tasks using Tasks.TaskCode as the key in the relation between them, and Entity Framework does not allow me to do that, any ideas :) ?
Note: I do not own this database, so any changes to the structure cannot be done.
You can try something like.
public class Certificates
{
[Key]
public int CertID { get; set; }
public Users User { get; set; }
public Quiz Quiz { get; set; }
}
public class Users
{
public int ID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class cpdContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entitiy<Users>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.User) // or WithOptional
.Map(m => m.MapKey("UserID")); //<- DB FK column name
modelBuilder.Entitiy<Quiz>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.Quiz) // or WithOptional
.Map(m => m.MapKey("QuizID")); //<- DB FK column name
}
}
Moreover, In independent association, the foreign key (Association) is defined in the conceptual model.
To define the association in a conceptual model, we must add association set, association and navigation properties.
It is represented as separate object in ObjectStateManager. It has its own EntityState!
When building association you always need entitites from both ends of association
This association is mapped in the same way as entity.
Source
There might be something I do not understand...
Why do you want to use Task.TaskCode as a foreign key of SubTasks ?
If ID is the PK of Tasks, then remove TaskCode an use Task.ID as you fk reference
SubTasks.TaskID ----> Task.ID.
Also, some other advices on naming conventions.
do not pluralize table names
use "<%table%>ID" as name for your pk : exemple TaskID for task.
use fk column as name for a fk : exemple "TaskID" as fk in your subtask table
If you are using MVC for this then you can simply make composite viewmodel for above.
Without changing in EF you can do what you want.
Make class as
public class CompositeTaskSubtask
{
public <namespace>.<tasktablename> taskmodel { get; set; }
public <namespace>.<subtasktablename> subtaskmodel { get; set; }
}
You can try adding a linking table called SubTask with its own primary key. That way taskCode will not need to be primary key.
CREATE TABLE Task
(
taskId int IDENTITY (1, 1) NOT NULL,
name nvarchar(50),
taskCode nvarchar(50),
CONSTRAINT Task_PK PRIMARY KEY(taskId)
)
GO
CREATE TABLE SubTask
(
subTaskId int NOT NULL,
taskId int NOT NULL, -- must have a parent
CONSTRAINT SubTask_PK PRIMARY KEY(subTaskId)
)
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK1 FOREIGN KEY (taskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO
ALTER TABLE SubTask ADD CONSTRAINT SubTask_FK2 FOREIGN KEY (subTaskId) REFERENCES [Task] (taskId) ON DELETE NO ACTION ON UPDATE NO ACTION
GO