Using Entity Framework Code first, I have several entities that share some fields, so I've created a base entity:
public class EntityBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTimeOffset CreationDate { get; set; }
}
public class Customer : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
public class Product : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
Now lets say that I want to provide a table for related images or attachments if you prefer, to those (and more of this) tables. An entity defined like:
public class Attachment
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
//For linking?
public Guid ParentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTimeOffset CreationDate { get; set; }
public byte[] Content { get; set; }
}
Is there a way of adding this relation to EntityBase so every entity that inherites it has a relation to the table and navigation properties?
I've spent hours on this and I hadn't found a way for it to work.
Edit: It finally happened not being possible as per v 1.1.1 Entity Framework Core does not support inheritance as I thought about it and it focuses it as a Table per Hierarchy representation of the DB: learn.microsoft.com/en-us/ef/core/modeling/relational/…
Just add the list of attachment to the base class, and a reference and referring id to the attachments. This works:
public class Attachment
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTimeOffset CreationDate { get; set; }
public byte[] Content { get; set; }
public Guid ParentId { get; set; }
public virtual EntityBase Parent { get; set; }
}
public class EntityBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTimeOffset CreationDate { get; set; }
public ICollection<Attachment> Attachments { get; set; }
}
public class Customer : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
public class Product : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
Here's the database schema generated from this model (you can see that the default TPH inheritance works by convention):
Problem is that Attachment cannot have a foreign key to either Customer or Product. The FK can point only to one table. So you need a common table where the FK can point to. One solution is shown above, where the one common table holds all the information for the entities in the inheritance tree (TPH inheritance).
The other solution is to use TPT inheritance. You still need a common table, where the FK can point to. That will be the EntityBases table. And then you can create two other tables, one for the Customer and one for the Product entity, which will use their own FK to refer to the correspoing EntityBases (through which, you can associate your attachments).
Whether this works or not depends on the actual EF-MySQL provider you are using. But if it is supported, here's a way you can configure it:
public class Attachment
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTimeOffset CreationDate { get; set; }
public byte[] Content { get; set; }
public Guid ParentId { get; set; }
public virtual EntityBase Parent { get; set; }
}
public class EntityBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTimeOffset CreationDate { get; set; }
public ICollection<Attachment> Attachments { get; set; }
}
public class Customer : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
public class Product : EntityBase
{
public string Code { get; set; }
public string Name { get; set; }
}
public class MyCtx : DbContext
{
public DbSet<Attachment> Attachments { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<Customer>().ToTable("Customers");
}
}
This will result in a different schema than before:
Related
I have this table using Entity Framework code first approach :
public class WebsitePart
{
public WebsitePart()
{
SubParts = new HashSet<WebsitePart>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public Nullable<int> ParentId { get; set; }
[StringLength(100)]
[Index("UQ_WebsitePart_Key", IsUnique = true)]
public string Key { get; set; }
public virtual WebsitePart Parent { get; set; }
public virtual ICollection<WebsitePart> SubParts { get; set; }
}
I need after getting the list of WebsiteParts to map it into List of another model where each element of this list has ParentId=null and down to the end of its grandsons(traversing) .
This the model I want to map to:
public class WebPartViewDto
{
public int Id { get; set; }
public string Key { get; set; }
public IList<WebPartViewDto> SubWebParts { get; set; }
}
Could you please show me how to map it with performance is taken into account?
I have two classes:
public class Address
{
public Guid Id { get; set; }
public virtual ICollection<ToAddressMessageLink> MessagesTo { get; set; }
public virtual ICollection<CopyAddressMessageLink> MessagesCopyTo { get; set; }
}
public class Message
{
public Guid Id { get; set; }
public virtual ICollection<ToAddressMessageLink> To { get; set; }
public virtual ICollection<CopyAddressMessageLink> CopyTo { get; set; }
}
And I need to save connection between them in a single table.
If I simply put many-to-many relation between, EF wouldn't realize what type of connection is actually set(table rows will be identical for them).
So I have to create a link class with a discriminator like following:
public class AddressMessageLink
{
public int LinkType { get; set; }
public Guid AddressId { get; set; }
public Guid MessageId { get; set; }
}
But it also doesn't work because I can't set link type/
So I have to use TPH here:
public abstract class AddressMessageLink
{
public int LinkType { get; set; }
public Guid AddressId { get; set; }
public Guid MessageId { get; set; }
}
public class CopyAddressMessageLink : AddressMessageLink
{
public virtual AddressDto Address { get; set; }
public virtual MessageDto Message { get; set; }
}
public class ToAddressMessageLink : AddressMessageLink
{
public virtual AddressDto Address { get; set; }
public virtual MessageDto Message { get; set; }
}
With a composite key:
HasKey(x=>new {x.AddressId, x.MessageId, x.LinkType});
But it doesn't work either, because EF:
"The foreign key component AddressId is not a declared property on type ToAddressMessageLink".
If I put AddressId and MessageId into derived class I can't set key because there is no component of it in base class.
What can I do it this situation?
If u have to classes, that you want to connect, you need to create a class that will connect yours two tables. And you did that there:
public class AddressMessageLink
{
public int LinkType { get; set; }
public Guid AddressId { get; set; }
public Guid MessageId { get; set; }
}
But you don't join virtual attributes. So you have to do like this.
In your tables definition:
public class Address
{
public Guid Id { get; set; }
public virtual ICollection<AddressMessageLink> AddressMessageLink { get; set; }
}
public class Message
{
public Guid Id { get; set; }
public virtual ICollection<AddressMessageLink> AddressMessageLink { get; set; }
}
And in AddressMessageLink object:
public class AddressMessageLink
{
public int LinkType { get; set; }
public Guid AddressId { get; set; }
public Guid MessageId { get; set; }
public virtual Address Address { get; set; }
public virtual Message Message { get; set; }
}
That will connect your tables as many to many.
I have a model:
public class Delivery
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid BusinessId { get; set; }
public virtual ICollection<PriceDisc> PriceDisc { get; set; }
}
public class PriceDisc
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
public Guid BusinessId { get; set; }
}
How to create relation one to many between Delivery.BusinessId and PriceDisc.BuissnessId?
Thanks
you have already created a one to many relationship between them Delivery hold collection of PriceDisc in your code so for one Delivery.BusinessId you have collection of PriceDisc.BusinessId
yesterday I created database in Management Studio and now I want to create it in program using EF Code First.
Here is link to my database: http://s11.postimg.org/6sv6cucgj/1462037_646961388683482_1557326399_n.jpg
And what I did:
public class GameModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreationTime { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string TotalTime { get; set; }
public DateTime RouteStartTime { get; set; }
public DateTime RouteEndTime { get; set; }
public int MaxPlayersPerTeam { get; set; }
public int CityId { get; set; }
public int CreatorId { get; set; }
[InverseProperty("Id")]
[ForeignKey("CreatorId")]
//public int TeamId { get; set; }
//[ForeignKey("TeamId")]
public virtual UserModel Creator { get; set; }
public virtual CityModel City { get; set; }
//public virtual TeamModel WinnerTeam { get; set; }
}
public class RegionModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<CityModel> Cities { get; set; }
}
public class CityModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int RegionId { get; set; }
public virtual RegionModel Region { get; set; }
public virtual ICollection<UserModel> Users { get; set; }
public virtual ICollection<GameModel> Games { get; set; }
}
public class UserModel
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public DateTime RegistrationDate { get; set; }
public string FacebookId { get; set; }
public int CityId { get; set; }
public virtual CityModel City { get; set; }
public virtual IEnumerable<GameModel> Games { get; set; }
}
For now I wanted to create 4 tables but I have some problems... I want to make CreatorId in GameModel, but it doesn't work... When i wrote UserId instead of CreatorId it was working ( without [InverseProperty("Id")] and [ForeignKey("CreatorId")]).
This is what i get:
The view 'The property 'Id' cannot be configured as a navigation property. The property must be a valid entity type and the property should have a non-abstract getter and setter. For collection properties the type must implement ICollection where T is a valid entity type.' or its master was not found or no view engine supports the searched locations.
edit:
I changed it like this:
public int CityId { get; set; }
public int CreatorId { get; set; }
[ForeignKey("CityId")]
public virtual CityModel City { get; set; }
[ForeignKey("CreatorId")]
public virtual UserModel Creator { get; set; }
And there is another problem.
The view 'Introducing FOREIGN KEY constraint 'FK_dbo.UserModels_dbo.CityModels_CityId' on table 'UserModels' 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.' or its master was not found or no view engine supports the searched locations.
And I have no idea how to solve it.
The InversePropertyAttribute specifies, which navigation property should be used for that relation.
A navigation property must be of an entity type (the types declared in your model, GameModel for example) or some type implementing ICollection<T>, where T has to be an entity type. UserModel.Id is an int, which clearly doesn't satisfy that condition.
So, the inverse property of GameModel.Creator could be UserModel.Games if you changed the type to ICollection<GameModel>, or had to be left unspecified. If you don't specify an inverse property, EF will try to work everything out on its own (in this case it would properly recognize GameModel.Creator as a navigation property, but UserModel.Games would most likely throw an exception, as it is neither an entity type, nor does it implement ICollection<T> with T being an entity type, nor is it a primitive type from a database point of view). However, EF's work-everything-out-by-itself-magic doesn't cope too well with multiple relations between the same entity types, which is when the InversePropertyAttribute is needed.
A quick example that demonstrates the problem:
class SomePrettyImportantStuff {
[Key]
public int ID { get; set; }
public int OtherId1 { get; set; }
public int OtherId2 { get; set; }
[ForeignKey("OtherId1")]
public virtual OtherImportantStuff Nav1 { get; set; }
[ForeignKey("OtherId2")]
public virtual OtherImportantStuff Nav2 { get; set; }
}
class OtherImportantStuff {
[Key]
public int ID { get; set; }
public virtual ICollection<SomePrettyImportantStuff> SoldStuff { get; set; }
public virtual ICollection<SomePrettyImportantStuff> BoughtStuff { get; set; }
}
Here, EF knows that it has to generate 2 FKs from SomePrettyImportantStuff to OtherImportantStuff with the names Id1 and Id2, but it has no way to tell which of the IDs refers to the entity where it was sold from and which is the one it was bought from.
Edit: How to fix the cyclic reference problem
To fix that problem, your context class should override OnModelCreating and configure the foreign keys which shouldn't cascade on delete accordingly, like this:
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<CityModel>().HasMany(c => c.Users).WithRequired(u => u.City)
.HasForeignKey(u => u.CityId).WillCascadeOnDelete(value: false);
// Add other non-cascading FK declarations here
base.OnModelCreating(builder);
}
Introduction
I am new to Entity Framework
I am using Code-first
Use-case
I have to following tables
[Table("TBL_UserVariant")]
public class UserVariant
{
[Key, Column(Order = 0)]
public int UserId { get; set; }
[Key, Column(Order = 1)]
public int VarId { get; set; }
public string Value { get; set; }
}
[Table("TBL_UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string eMail { get; set; }
}
I want TBL_UserProfile to refer a list of all TBL_UserVariant entries where TBL_UserProfile::UserId == TBL_UserVariant::UserId
The following is an example of my aim
[Table("TBL_UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string eMail { get; set; }
public UserVariant[] variants;
}
Where 'UserProfile::variants' should include a list of items where 'TBL_UserProfile::UserId == TBL_UserVariant::UserId'
Question
Is this directly possible using EF ? OR, should I implement a wrapper populating 'UserProfile::variants' ~manually~ ?
You have to just add a navigation property to the UserProfile entity.
[Table("TBL_UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string eMail { get; set; }
public virtual ICollection<UserVariant> UserVariants { get; set; }
}
The following is what you should need. EF will take care of the rest.
[Table("TBL_UserProfile")]
public class UserProfile
{
[Key]
public int UserId { get; set; }
public string eMail { get; set; }
public virtual ICollection<UserVariant> Variants { get; set; }
}
[Table("TBL_UserVariant")]
public class UserVariant
{
[Key]
public int VarId { get; set; }
public UserProfile User { get; set; }
public string Value { get; set; }
}
I think what you're asking for is that you want to have one UserProfile, mapped to Many UserVariants
In that case you'll need to add a collection to your UserProfile class.
public virtual ICollection<UserVariant> UserVariants { get; set; }
You'll also need to fix the [Key] property on the UserVariant class as I believe it should be on VarId. You can then just specify a navigation property
[Key]
public int VarId { get; set; }
public UserProfile User { get; set; }
Edit
As an aside, your naming conventions are all over the place. Don't prefix your table names with TBL_. Capitalise all your properties/Columns. e.g. Email not eMail