I'm attempting to build a 1-1 relationship - a Tenant has a Url, and vice versa:
Models
public class Tenant {
[Key]
[Required]
public int TenantId { get; set; }
public string Name { get; set; }
public virtual Url Url { get; set; }
public int UrlId { get; set; }
}
public class Url {
[Key]
[Required]
public int UrlId { get; set; }
public string Name { get; set; }
public virtual Tenant Tenant { get; set; }
public int TenantId { get; set; }
}
Configs
public class UrlConfiguration : EntityTypeConfiguration<Url> {
public UrlConfiguration() {
HasKey(s => s.UrlId);
}
}
public class TenantConfiguration : EntityTypeConfiguration<Tenant>
{
public TenantConfiguration() {
HasRequired(s => s.Url).WithRequiredPrincipal(s => s.Tenant);
}
}
Result:
I'd expect there to be a foreign key on both models... why is this not the case?
A one-to-one relationship with both ends having required foreign keys cannot exist in a relational database. If saving a new Tenant record requires a URL record, but in order to create that URL record, that URL requires a Tenant record, where will you begin?
Even though on a database level it can't practically exist, this model will still work. From my experience, Entity Framework will enforce the dependency on application level, and will throw an EntityException when it detects that one of the entities you're trying to save has no relationship to one of the other.
It creates this database model so that it can still save your entities, and enforce relationships on an application level.
No, this isn't nice on a database level as the one-to-one constraint won't be enforced there. If you need the database constraints as well, consider merging the tables or redesigning your data structures so that a one-to-one relationship isn't necessary.
Related
Many times I have a general purpose entity that other entities contain a collection of. I don't want to have a new collection entity for each parent entity type that needs it but would like to re-use a single general purpose entity. For performance reasons, I also don't want to explicitly define many-to-many relationships as in this answer. The simplest example would be a collection of strings.
public class MyString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyString> { get; set; }
}
public class MyOtherString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherString> { get; set; }
}
I'd really like to combine MyString and MyOtherString into a single entity:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
Except now I'm going to have an additional foreign key in GeneralPurposeString for every entity that contains a collection of GeneralPurposeString.
What I would like would be a way to have an additional parent category column on the GeneralPurposeString table (but not the entity) that would specify which entity the item belongs to. I use Guid for primary keys, so the tables could look something like this:
CREATE TABLE [GeneralPurposeString]
(
[Id] uniqueidentifier NOT NULL
CONSTRAINT PK_GeneralPurposeString PRIMARY KEY,
[ParentEntityCategory] uniqueidentifier NOT NULL,
[ParentEntityId] uniqueidentifier NOT NULL,
[Value] nvarchar(MAX)
)
And some how in Code First to specify that MyEntity has a certain category, and that it's collection of GeneralPurposeString uses that category, and MyOtherEntity uses another category (Guid) for it's collections of GeneralPurposeString.
The key would be that GeneralPurposeString could be a collection in any other entity and that loading the parent entity and including the collection would automatically load without having to explicitly specify the category.
The purposes for all of this are
Allow .NET code to have GeneralPurposeString code that wasn't replicated everywhere (actual utility or business logic code). This can probably also be accomplished through inheritance and explicit mapping but that would still leave multiple tables in the database (see #2).
Have only one table in the database for GeneralPurposeString. This is more of a tidiness issue. Performance would possibly be better with multiple tables, but indexing on ParentEntityCategory/ParentEntityId and covering Value should be good performance for lookups.
Not have to explicitly code this relationship and the lookups everywhere it's needed.
I'm thinking if I can get over #2 and be OK with a separate table behind the scenes and implementing a derived class, that will be the simplest route to go.
So just:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
// It's just a GeneralPurposeString with a fancy MyEntity membership pin
public class MyEntityString: GeneralPurposeString {}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyEntityString> Strings { get; set; }
}
// Cool GeneralPurposeStrings belong to MyOtherEntity
public class MyOtherEntityString: GeneralPurposeString {}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherEntityString> Strings { get; set; }
}
public class MyContext: DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
}
I don't have to add the derived classes to the DbContext and the tables get named with the plural of the derived class by default, so it's actually pretty straight forward.
My previous train of thought with the Parent Category would require additional coding/annotation even if EF supported it. This uses purely convention and nothing extra needed in annotations or in OnModelCreating().
I'm not seeing any harm in extra tables at this point in time. I don't see a need (currently) to have all of the data in one table for reporting, but that really depends on the type of general purpose entity, so I may need to revisit this in the future, or I may just take the many-to-many route if I do need the data in one table.
And I can still have:
public static class GeneralPurposeStringExtensions
{
public static void SassThatHoopyFrood(this GeneralPurposeString s)
{
// do stuff
}
}
I have a property on my items class called vend_id which of course EF thinks is a foreign key to the vendor table. It actually should be a foreign key in the database but for reasons unknown to me the designers of the db chose not to make it a foreign key.
I am using EF to create a copy of the db schema on the local machine. When EF creates the database I want to tell it not to create a foreign key on the vend_id column. How do I do that? Ideally I do not want to rename the property because there are several such instances in my db and it just makes it confusing.
Thank you,
Sam
You can't have a navigation property to a Vendor entity in your Item entity class if the Items table does not have a foreign key to table Vendor. If you did not specify a navigation property in entity class Item, EF would not infer that vend_id is a foreign key.
Update:
Unable to reproduce with the following:
[Table("EntityA")]
public partial class EntityA
{
public int Id { get; set; }
public Nullable<int> EntityBId { get; set; }
public string Description { get; set; }
[ForeignKey( "EntityBId" )]
public virtual EntityB EntityB { get; set; }
// this is not created as a FK
// nor does EntityCId cause a FK
public int EntityC_Id { get; set; }
}
[Table("EntityC")]
public class EntityC
{
public EntityC()
{
EntitiesD = new HashSet<EntityD>();
}
public int EntityCId { get; set; }
public string Name { get; set; }
public virtual ICollection<EntityD> EntitiesD { get; set; }
}
This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}
I have a problem with the Entity Framework.
public class User : Receiver
{
public User()
{
if (Groups == null)
Groups = new List<Group>();
if (Buddies == null)
Buddies = new List<User>();
}
[Required]
public string PhoneNumber { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
[ForeignKey("Guid"), JsonIgnore]
public IList<Group> Groups { get; set; }
}
public class Receiver
{
public Receiver()
{
Guid = Guid.NewGuid();
Created = DateTime.Now;
}
[Key]
public Guid Guid { get; set; }
[Required]
public DateTime Created { get; set; }
}
When i try to add a user...
User user = new User
{
Guid = new Guid("8cd094c9-e4df-494e-b991-5cf5cc03d6e3"),
PhoneNumber = "+4991276460"
};
cmc.Receivers.Add(user);
... it ends in follogwing error.
The object of the Type "System.Collections.Generic.List`1[Project.Models.User]" can't be converted to "Project.Models.User".
When i comment out following two lines:
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...the programm runs fine.
I hope someone can help me to fix this problem.
Otherwise it runs into an error at this line : cmc.Receivers.Add(user);
In your mapping...
[ForeignKey("Guid"), JsonIgnore]
public IList<User> Buddies { get; set; }
...you specify that User.Buddies is part of a one-to-many relationship and that User.Guid (=Receiver.Guid) is the foreign key in this relationship. But User.Guid is also the primary key, hence it must be unique. As a result a User cannot have a list of Buddies but only a single reference.
The mapping makes no sense but the exception is not very helpful and difficult to understand. (Somehow EF seems to recognize internally that the Buddies cannot be a list with that mapping and wants to cast the list to a single reference. It should detect in my opinion that the mapping is invalid in the first place.)
For a correct one-to-many mapping you need a foreign key that is different from the primary key. You can achieve that by either removing the [ForeignKey] annotation altogether...
[JsonIgnore]
public IList<User> Buddies { get; set; }
...in which case EF will create a default foreign key in the Receivers table (it will be some column with an underscore in its name, but you can rename that with Fluent API if you don't like the default name) or by adding your own foreign key property to the User class:
public Guid? BuddyGuid { get; set; }
[ForeignKey("BuddyGuid"), JsonIgnore]
public IList<User> Buddies { get; set; }
I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.
When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!