Entity Framework Core model one-to-one - entity-framework

I'm working with EF Core 1.1 on an asp.net mvc core project and stumbled upon, what I think, is non optimal SQL generation. But hey, I'm not an expert so I might be completly wrong :)
Models
public class Influencer
{
public int Id { get; set; }
public Instagram Instagram { get; set; }
public YouTube YouTube { get; set; }
// In the future, more social channels will be added so
// this is another concern I have how to architect/model properly
}
public class Instagram
{
public int Id { get; set;}
public string UserId { get; set; }
// More properties such as bio, image url, website and other stuff
}
public class YouTube
{
public int Id { get; set; }
public string ChannelId { get; set; }
// More properties such as bio, image url, statistics etc
}
The UserId and ChannelId are the "natural keys" (pardon my abuse of the correct SQL term :) and the Id properties "artifical keys" in a hope to create an interface/base class to simply various operations such as delete, refresh.
So to the question - how to correctly set this up in the modelBuilder to be correct?
The SQL I think should be the best is something like
Instagram
- Id (PK)
- UserId (AK)
- InfluencerId (FK)
YouTube
- Id (PK)
- ChannelId (AK)
- InfluencerId (FK)
Influencer
- Id
This should in effect constraint a one-to-one relationship between an influencer and its connected social platforms as well constraint that no influencer share the same social platform (just writing it make me unsure if I really want that since there might be cases where a couple for example share the same youtube-channel but are two individual influencers. Guess this is easier to achieve given the above design than introducing it later on)
On the the current modelling
modelBuilder
.Entity<InstagramChannel>()
.HasAlternateKey(i => i.UserId)
.HasName("AK_UserId");
modelBuilder
.Entity<Profile>()
.HasOne<Instagram>()
.WithOne()
.HasForeignKey(typeof(Instagram), "ProfileId")
.HasConstraintName("FK_Instagram_Profile")
.OnDelete(DeleteBehavior.Cascade);
This however generates a key to each of the social platforms on the Influencer such as Influencer.InstagramId, Influencer.YouTubeId causing two changes to database when a social platform is deleted.
Edit
So marking the properties Instagram, YouTube as not mapped produced the SQL that I was looking for. But now I'm uncertain whether or not I have missed something out - perhaps there is some optimizations enabled by EF core if the "parent table" has a direct relation to its related tables. Perhaps this just make it impossible for EF to evaluate the Include(i => i.Instagram) statement since it probably want to do so without the need for a join.
So quite a long post and perhaps not really a question (well, I said it :) and probably not suitable in this forum. But hey, there are many great guys and girls out there that might wan't to share their knowledge!
Thanks

You can achieve your goal even without using modelBuilder, using attributes:
public class Influencer
{
public int Id { get; set; }
[InverseProperty("Influencer")]
public Instagram Instagram { get; set; }
[InverseProperty("Influencer")]
public YouTube YouTube { get; set; }
}
public class Instagram
{
public int Id { get; set;}
public string UserId { get; set; }
public int InfluencerId { get; set; }
[ForeignKey("InfluencerId")]
public virtual Influencer Influencer { get; set; }
}
public class YouTube
{
public int Id { get; set; }
public string ChannelId { get; set; }
public int InfluencerId { get; set; }
[ForeignKey("InfluencerId")]
public virtual Influencer Influencer { get; set; }
}
You may remove InfluencerId properties if you don't like them (I prefer to have explicit Id field to be able to set int-value there instead of assigning Influencer instances and reading them from DB only for this).
Also, you may remove virtual keywords, because EF Core (currently) do not require them (but it also does not support LazyLoading yet, so I prefer to keep "virtual" in case it will be required later)

Related

Web API oData - Should I forget about view models?

Ive been working with MVC for a while now and am used to creating a View Model class for every MVC view. Now I am trying out Web API and I think I may be hung up on this MVC mentality. My relationship looks like this:
public class Supplier
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<SupplierProduct> SupplierProducts { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<SupplierProduct> SupplierProducts { get; set; }
}
public class SupplierProduct
{
public int Id { get; set; }
public string Title { get; set; }
public int SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
I am working on the creation of the supplier where in the create form the user is able to select multiple products that already exist. In MVC, I would POST a view model that looks something like this:
public class SupplierCreateViewModel
{
public string Title { get; set; }
public ICollection<ProductViewModel> SelectedProducts { get; set; }
}
And in the controller I would first create the new Supplier then create a new SupplierProduct for each SelectedProduct. I implemented something like this in Web API in the POST action of my Supplier oData controller but it doesnt feel right. I think that instead, I need to change my approach and do something like this from the client:
Scrap the View Model design. (There arent really 'views' anymore anyway)
Have both a Supplier and a SupplierProduct Controller with a POST action on both.
On save, send my Supplier create request to POST api/Suppliers/.
Using the Id of the Supplier JSON in the response, send multiple create requests to POST api/SupplierProduct.
So my questions are:
Am I heading in the right direction with this approach?
Instead of View Models is there a different pattern I should use? DTO?
With the example given, am I forced to send 1 - n requests like that? This feels wrong.
Actually, it depends on your use-case. If your API is totally faced publicly, i would advice using DTO's. If it is for yourselve or for an internal team, i would stick with OData EF Models ( because it is quicker)
You can ( as usual) give the entire entity through your api.
You can use a viewmodel ( more like DTO's when using it in an API, but it's the same thing) and transform the methods accordingly. You can use automapper for that - it also transforms the $filter query, an example is found here : Web API Queryable - how to apply AutoMapper?.
Don't forget, an API has a lot of awesome advantages. OData uses Batch and Patch to change your entities. So i personally stick with Odata as entites most of the time, but that's a personal choice.

One to many relationship: why does ID field go in other entity?

In Entity Framework when I want to specify that an entity has many of another type of entity it seems to do things backwards to me.
For instance let's say I have a keyword entity that is used in several places throughout my app. All keywords are unique but on my other entities I want to have multiple keywords attached them so to me this would make sense:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
}
However when I do this the foreign key is added to the Keyword table whereas I want it to be on the actual entity so I can look at it in database and see small list of keywords instead of looking at keyword and seeing a ridiculous large number of page results.
So instead to get Entity Framework to put the Keyword_IDs on Page and Search entities I am doing this:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Page> Pages { get; set; }
public virtual ICollection<Search> Searches { get; set; }
}
This feels backwards as I am specifying the relationship on the entity that doesn't get the foreign ID field in the database table.
I feel like I am doing something wrong as I should be able to see the relationship by looking at my search & page class.
I am sorry for the basic question but for some reason I have read documentation and I am not fully understanding it.
In a one-to-many association it's always the many side that refers to the one side. How else would you implement it? If a Page would have a KeywordId as FK, it could only have one keyword, ever.
Also, even when a Keyword would belong to a myriad of pages, that doesn't mean you always have to access all of these pages through one keyword. You'd only do that if you'd do a search for pages in which specific keywords are used.
But now back to your model. You can't have one-to-many associations here. It would mean that any keyword can only belong to one Page or one Search. And if you invert the relationship, as you proposed, a Page or Search can only ever have one keyword (the one that Keyword_ID refers to).
In reality, you're dealing with many-to-many associations and the good news is, it leaves your Keyword intact.
Modelling it as many-to-many doesn't change the way your model looks (the first version), but the mapping is different:
modelBuilder.Entity<Page>().HasMany(p => p.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("PageKeyword");
m.MapLeftKey("PageID");
m.MapRightKey("KeywordID");
});
modelBuilder.Entity<Search>().HasMany(s => s.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("SearchKeyword");
m.MapLeftKey("SearchID");
m.MapRightKey("KeywordID");
});
This will generate two junction tables in your database, PageKeyword and SearchKeyword that record the many-to-many associations.

Fluent API meaning in ASP.NET MVC when using Entity Framework

I am learning ASP.NET MVC. And In order to create PK and FK I have added some code in models in .cs file as below
public class Courses
{
public int CourseID { get; set; }
[Key]
public string CourseName { get; set; }
public string Description { get; set; }
public int Duration { get; set; }
public virtual ICollection<Instructors> Instructors { get; set; }
}
public class Instructors
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CourseName { get; set; }
[ForeignKey("CourseName")]
public virtual Courses Courses { get; set; }
}
My question is then what is the significance of Fluent API. I have successfully created PK and FK using above code in model. Then what is Fluent API and why it is needed?
Entity framework Fluent Api is an alternative way to define database schema using Entity framework Code First approach. The syntax that you used in your question uses data annotations which is other common approach.
The major advantage of FluentApi is that you don't have to have actual access to your model classes in order to decorate them. For example when you have your models in separate assembly decorating them with annotations is simply impossible. On the other hand by using EF FluentAPI you can easily do it with something like:
modelBuilder.Entity<Courses>().HasKey(c => c.CourseID);
At the bottom line both approaches generate exactly the same database schema.
So if you prefer to use data annotations and your project structure allows it, you can use it without any doubts. In those cases when you can not use the data annotations approach and you still want to use Code First approach, FluentApi is the right way to look at.

EF Code First Optional Navigation

I have the following POCO objects:
public class Chat
{
public int ChatID { get; set; }
public virtual Audio ChatAudio { get; set; }
public virtual Video ChatVideo { get; set; }
}
public class Audio
{
public int AudioID { get; set; }
public int ChatID { get; set; }
public Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
public class Video
{
public int VideoID { get; set; }
public int ChatID { get; set; }
public Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
DBA has the following tables setup
-Chat-
ChatID
-Audio-
AudioID
ChatID <-- FK, Unique Index
Recorded
-Video-
VideoID
ChatID <-- FK, Unique Index
Recorded
So this is a 1-to-optional relationship. He doesn't want to create a nullable column for Audio or Video on the Chat table. How can I force this to work in EF Code First? The only way I've gotten this to work is to make those navigational properties collections and defined HasMany(). I do not want that. I want to be able to do:
if(Chat.Audio == null || Chat.Video==null)
{
// Do stuff
}
I appreciate any suggestions you may have.
No, you cannot map this model to your database schema with Entity Framework (no matter which version up to current version 4.2).
The main reason is that EF doesn't support unique key constraints, which means: mapping a reference navigation property to a column in the database which has a unique key constraint. EF will consider such a FK column in the database always as not unique and doesn't see the unique key constraint. So, the ends of the two associations in Chat must be collections. Or you could not expose them in your model, but internally EF will consider them always as "many"-ends.
The only true one-to-one relationship which EF supports are shared primary key relationships. It would mean that you don't have a ChatId column in the Audio and Video table but instead the primary key columns AudioId and VideoId are foreign key columns to the Chat table at the same time. Obviously the primary key columns cannot be all autogenerated identities in the database to make this kind of mapping possible.
A great description for both one-to-one mapping strategies and a comparison of their benefits and limitations can be found here:
Shared Primary Key Associations
One-to-One Foreign Key Associations
You could "cheat" and configure the the relationship as one to many, enforcing the one to one in the object model (also, in the datacontext initializer you can manually add the unique constraint to the database).
It would basically look something like this
public class Chat
{
public int ChatID { get; set; }
protected virtual ICollection<Audio> HiddenAudioCollection{get;set;}
public Audio ChatAudio
{
get{return HiddenAudioCollection.FirstOrDefault();}
set
{
if(HiddenAudioCollection==null)
HiddenAudioCollection=new List<Audio>();
if(HiddenAudioCollection.Any())
{
//decide if you throw an exception or delete the existing Audio
}
HiddenAudioCollection.Add(value);
}
}
//Do the same with ChatVideo
}
Another option, since the Video and Audio "belong" to the chat is to use a complex structure. Don't give the Video and Audio their own Id, they will be mapped to the chat table. I know that some DBA's hate this idea because it's optional data. But technically it's no different than storing an Address in a Customer table directly. It all depends on how normalized you want your database to be.
Let me make sure I understand this correctly:
Chat has zero or one Audio, and zero or one Video.
Audio, if it exists, belongs to exactly 1 Chat.
Video, if it exists, belongs to exactly 1 Chat.
Is this correct? If so, would your DBA mind a schema like this?
-Chat-
ChatID
-Audio-
ChatID <-- FK, Primary Key
Recorded
-Video-
ChatID <-- FK, Primary Key
Recorded
If so, I think you can do this:
public class Chat
{
public int ChatID { get; set; }
public virtual Audio ChatAudio { get; set; }
public virtual Video ChatVideo { get; set; }
}
public class Audio
{
public int ChatID { get; set; }
public virtual Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
public class Video
{
public int ChatID { get; set; }
public virtual Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Audio>().HasKey(a => a.ChatID);
modelBuilder.Entity<Video>().HasKey(a => a.ChatID);
var chat = modelBuilder.Entity<Chat>();
chat
.HasOptional(c => c.ChatAudio)
.WithRequired(a => a.Chat);
chat
.HasOptional(c => c.ChatVideo)
.WithRequired(a => a.Chat);
...
}

Entity Framework POCO Does Not Fit Nicely with Domain Objects

I have taken a model first approach for a project i'm working on. An example of a class relationship is shown as follows, pretty strightforward:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
List<Photo> Photos { get; set; }
}
public class Photo
{
public int Id { get; set; }
public string Path { get; set; }
}
The database schema will roughly be:
--------------
Products Table
--------------
Id int,
Name Varchar
------------
Photos Table
------------
Id int,
Path varchar
ProductId int FK Products.ID
A Product can have Zero or more Photos.
Now when i try to plug is my ORM of choice (Entity Framework V4 - Poco approach) iam forced to map my relationships in the domain model!
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
List<Photo> Photos { get; set; }
}
public class Photo
{
public int Id { get; set; }
public string Path { get; set; }
public int ProductId {get; set; } //Foriegn Key
public Product Proudct {get; set; } //For uni-directional navigation
}
Firstly, i dont need/want uni-directional navigation. I understand this can be deleted. Secondly, I dont want the Foriegn Key declared in the Photos class.
I dont think this is true POCO/persistence ignorance if i must define database properties in the Domain Objects?
Do other ORM's behave this way?
I found the answer. Using the wizard, there is an option to "Include foreign key columns in the model" - Uncheck this box and you will a clean conceptual model without FK.
Make sure Code Generation Strategy is set to none in the properties window.
Why don't you want to have Photo.Product property? If there is no such property, it seems one photo can belong to several products and since database schema should be more complex (with auxiliary table).
The relationships don't have to be two-way, and don't have to be public (if you use true POCOs, not proxy types). You've said quite a bit about what you don't want in your code, but can you be clearer about how you do want to define the relationships? It has to go somewhere. Where would you like to put it? There are many options.