When i link two entities to one with the same herited navigation property i can't generate my db..
I saw i can duplicate the property "House" and specify the InverseProperty on each of herited types but i wan't centralize the property.
(naturally i have more of code associated at the property)
public abstract class DbEntity
{
[Key]
public int Id { get; set; }
}
[Table("Houses")]
public class House : DbEntity
{
public string Color { get; set; }
[InverseProperty("House")]
public virtual ICollection<Garden> Gardens { get; set; }
[InverseProperty("House")]
public virtual ICollection<Room> Rooms { get; set; }
}
public abstract class HousePart : DbEntity
{
public string Color { get; set; }
public int HouseId { get; set; }
[ForeignKey("HouseId")]
public virtual House House { get; set; }
}
[Table("Gardens")]
public class Garden : HousePart {}
[Table("Rooms")]
public class Room : HousePart {}
House: FromRole: NavigationProperty 'House' is not valid. Type 'Room' of FromRole 'House_Rooms_Target' in AssociationType 'House_Rooms' must exactly match with the type 'Garden' on which this NavigationProperty is declared on.
Thanks if you have a good way to centralize the implementation of property.
Well it's work like that :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Garden>()
.HasRequired(c => c.House)
.WithMany(d => d.Gardens)
.HasForeignKey(d => d.HouseId);
modelBuilder.Entity<Room>()
.HasRequired(c => c.House)
.WithMany(d => d.Rooms)
.HasForeignKey(d => d.HouseId);
base.OnModelCreating(modelBuilder);
}
Related
I have two DbContext. A BaseDbContext and one that inherits from the BaseDbContextcalled FemaleDbContext.
public class BaseDbContext : DbContext
{
public BaseDbContext(DbContextOptions options) : base(options) { }
public virtual DbSet<Person> Person { get; set; }
public virtual DbSet<House> House { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().ToTable("Person", "dbo");
modelBuilder.Entity<House>().ToTable("House", "dbo");
modelBuilder.Entity<Person>().HasOne(e => e.House).WithMany(e => e.Persons);
modelBuilder.Entity<House>().HasMany(e => e.Persons).WithOne(e => e.House);
}
}
The goal is to extend the Person entity with another property. I do not want to use shadow properties because its too dynamic. So I am trying to make it work using TPH. Here is my other context:
public class FemaleDbContext : BaseDbContext
{
public DbSet<Female> Female { get; set; }
public FemaleDbContext(DbContextOptions<FemaleDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Female>().HasBaseType<Person>();
base.OnModelCreating(modelBuilder);
}
}
As you can see, my sub-context should use the Female entity instead of the Person. The problem is that when I run this.Context.Female.ToList() on my SubDbContext, only entities with the value Female inside the Discriminator field inside my database are returned. Entities with the value Person in that table are returned. But I want to get every entity.
Also, here are my entities:
public class Person
{
public int Id { get; set; }
public string Firstname { get; set; }
public string Middlename { get; set; }
public string Lastname { get; set; }
}
public class Female : Person
{
public bool? IsPregnant { get; set; }
}
How can I configure my DbContext that this.Context.Female.ToList() returns both Females and Persons. Note that this.Context.Person.ToList() already returns everything, not only Persons
I'm learning EF Core and the below is my three POCOs:
public class County
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class City
{
[Key]
public int cid { get; set; }
public string cname { get; set; }
}
public class People
{
[Key]
public int pid { get; set; }
public string pname { get; set; }
public int cid { get; set; }
public City WhichCity { get; set; }
}
I'm expecting two foreign keys but only got one from City table. How to make it(using annotation or fluent API or whatever) except explicitly define a County variable to People class.
Just want to clarify: you don't need to have navigation properties, i.e., public City City { get; set; } in order to setup relationships. The only things you need are the foreign key and proper configurations.
I think the following configuration would work for you (not tested though):
Entities
Here I also purposely modified your existing classes to follow C# Naming Conventions, if you care. Remember, if you're doing Code First, that means you can have your classes however you want first. You think about persistence later on. Actually I will show you how you can rename classes' properties when you persist them to your database via Configurations.
public class County
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
public class People
{
public int Id { get; set; }
public string Name { get; set; }
public int CityId { get; set; }
// Optional
//public City City { get; set; }
public int CountyId { get; set; }
// Optional
//public County County { get; set; }
}
Configurations
Instead of using Data Annotation, you can use Fluent API with configurations to configure how you want to map your classes back to database.
public class CountyConfiguration : IEntityTypeConfiguration<County>
{
public void Configure(EntityTypeBuilder<County> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_county"); // If you want to rename the table
}
}
public class CityConfiguration : IEntityTypeConfiguration<City>
{
public void Configure(EntityTypeBuilder<City> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("cid"); // If you want to rename to "cid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("cname"); // If you want to rename to "cname"
builder.ToTable("so_city"); // If you want to rename the table
}
}
public class PeopleConfiguration : IEntityTypeConfiguration<People>
{
public void Configure(EntityTypeBuilder<People> builder)
{
builder.HasKey(x => x.Id); // Same as using [Key]
builder.Property(x => x.Id)
.HasColumnName("pid"); // If you want to rename to "pid"
builder.Property(x => x.Name)
.IsRequired() // If you want to mark that field required
.HasColumnName("pname"); // If you want to rename to "pname"
// Relationship
builder.HasOne<County>() // People has one County
.WithMany() // County has many people
.HasForeignKey<County>(x => x.CountyId); // Foreign key is CountyId
builder.HasOne<City>() // People has one City
.WithMany() // City has many people
.HasForeignKey<City>(x => x.CityId); // Foreign key is CityId
builder.ToTable("so_people"); // If you want to rename the table
}
}
And lastly, you need to apply those configurations OnModelCreating:
public class YourDbContext : DbContext
{
public DbSet<County> Counties { get; set; }
public DbSet<City> Cities { get; set; }
public DbSet<People> People { get; set; }
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new CountyConfiguration());
builder.ApplyConfiguration(new CityConfiguration());
builder.ApplyConfiguration(new PeopleConfiguration());
}
}
DISCLAIM: wrote it by hand. Not tested.
My apologies for (perhaps) not using the right terms in the title and this post.
The problem is as follows:
I have a POCO class which has a reference to another table (which is read only). This table has a one-to-one relationship with the other table.
I have set this upo as follow:
public class Commodity
{
public Commodity()
{
}
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
The OmsCommodityMaterial property is the referenced table. This referenced table is also a POCO class which has some other fields, and a porperty back to my own (Commodity) table so I can make a one-to-one relationship with Fluent:
public class OmsCommodityMaterial : OmsBaseClass
{
public OmsCommodityMaterial()
{
}
public long? CommodityMaterialID { get; set; }
public long? CommodityID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
public Commodity Commodity { get; set; }
}
Fluent (for the one-to-one relation) is set up as follows:
public class MyContext : IdentityDbContext<ApplicationUser>
{
public virtual DbSet<Commodity> Commodity { get; set; }
// Oms classes:
public virtual DbSet<OmsCommodityMaterial> OmsCommodityMaterial { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
}
}
In my endpoint I want to do a GET of all values which return the specific fields of my own table (Commodity) and all the fields of the referenced table (OmsCommodityMaterial).
For this purpose I created a ViewModel (also because else I get a circular reference as I found out in this post: ERR_CONNECTION_RESET returning Async including object child collections) which looks as follow:
public class CommodityViewModel
{
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
For the ViewModels I am using AutoMapper, but I actually have no clue how I can map / return the list of the above ViewModel.
UPDATE
I ended up eliminating the Circular reference error by adding the [JsonIgnore] attribute to the public virtual Commodity Commodity { get; set; } property in the OmsCommodityMaterial POCO class. Now I can get all the needed column values:
return await this.Context.Commodity
.Include(i => i.OmsCommodityMaterial)
.ToListAsync();
Though, I suppose this is not the way to go. There should be a better solution for this by creating a ViewModel that retrieves the Commodity columns and (some) of the referenced OmsCommodityMaterial columns without falling in the Circular Reference error, but how (using AutoMapper)?
The problem
I'm trying to share a large table (200+fields) around ~7 entities using table splitting as per my previous question.
EF6 requires navigation properties not just from the primary model to the child models, but between all the child models (which sucks).
Manual solution
This can be done manually:
public class Franchise
{
[Key]
public int Id { get; set; }
public virtual FranchiseEntity Entity { get; set; }
public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}
[Table("Franchise")]
public class FranchiseEntity
{
[Key]
public int Id { get; set; }
public virtual FranchiseEntity Entity { get; set; } // Ignored, but relevant when inheritance involved, below...
public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}
[Table("Franchise")]
public class FranchiseMiscellaneous
{
[Key]
public int Id { get; set; }
public virtual FranchiseEntity Entity { get; set;
public virtual FranchiseMiscellaneous Miscellaneous { get; set; } // Ignored, but relevant when inheritance involved, below...
}
With fluent mappings:
public class FranchiseMapping : EntityTypeConfiguration<Franchise>
{
public FranchiseMapping()
{
HasRequired(x => x.Entity).WithRequiredPrincipal();
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal();
}
}
public class FranchiseEntityMapping : EntityTypeConfiguration<FranchiseEntity>
{
public FranchiseEntityMapping()
{
Ignore(x => x.Entity);
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Entity);
}
}
public class FranchiseMiscellaneousMapping : EntityTypeConfiguration<FranchiseMiscellaneous>
{
public FranchiseMiscellaneousMapping()
{
Ignore(x => x.Miscellaneous);
}
}
THIS WORKS. But it won't this will not scale up well with 7+ models.
Attempt to improve #1
I'd like to improve through Inheritance + DRY principle:
public abstract class SharedFranchiseIdBase
{
[Key]
public int Id { get; set; }
public virtual FranchiseEntity Entity { get; set; }
public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}
public class Franchise : SharedFranchiseIdBase { ... }
public class FranchiseEntity : SharedFranchiseIdBase { ... }
public class FranchiseMiscellaneous : SharedFranchiseIdBase { ... }
// Maybe generalize the mapping code too...
But this FAILS on first request with "Sequence contains more than one matching element":
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: Sequence contains more than one matching element
Result StackTrace:
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
at System.Data.Entity.ModelConfiguration.Configuration.Properties.Navigation.NavigationPropertyConfiguration.ConfigureDependentBehavior(AssociationType associationType, EdmModel model, EntityTypeConfiguration entityTypeConfiguration)
at System.Data.Entity.ModelConfiguration.Configuration.Properties.Navigation.NavigationPropertyConfiguration.Configure(NavigationProperty navigationProperty, EdmModel model, EntityTypeConfiguration entityTypeConfiguration)
at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.ConfigureAssociations(EntityType entityType, EdmModel model)
at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure(EntityType entityType, EdmModel model)
at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntities(EdmModel model)
at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
... // my test query function
Attempt to improve #2
I thought I could declare them abstract, so at least the programmers are forced to implement the correct members (still sucks to re-declare on each derived class):
public abstract class SharedFranchiseIdBase
{
[Key]
public int Id { get; set; }
public abstract FranchiseEntity Entity { get; set; }
public abstract FranchiseMiscellaneous Miscellaneous { get; set; }
}
public class Franchise : SharedFranchiseIdBase
{
[Key]
public int Id { get; set; }
public override FranchiseEntity Entity { get; set; }
public override FranchiseMiscellaneous Miscellaneous { get; set; }
}
//etc for other classes
But this fails when the same error. Huh?? The class definitions are IDENTICAL as the working copy except that they are declared "override" instead of "virtual". It's as if E/F is indexing on PropertyInfos or something without regard for the PropertyInfo.ReflectedType
Attempt to improve #3
I could enforce the pattern using an interface, but this is less preferable as the interface has to be declared on each class which is starting to look pretty weird:
public class Franchise : SharedFranchiseIdBase, ISharedFranchiseId { ... }
public class FranchiseEntity : SharedFranchiseIdBase, ISharedFranchiseId { ... }
public class FranchiseMiscellaneous : SharedFranchiseIdBase, ISharedFranchiseId { ... }
Huh?
Is this a bug in E/F, that it struggles to treat properties on the base class identical to those on the derived classes?
Apologies for the long-winded explanation, it's the summary of this morning's entire investigation.
In the end I have decided to adopt the manual solution as I could not get any of the improvement attempts to work.
The code & models aren't elegant, but at the end of the day it performs OK. I've implemented the pattern in 3 areas and it's performing as required, in the Domain and at the SQL layer.
To ease the pain and provide developers with a consistent way to work with this pattern, I created this interface to enforce all the relationships:
public interface ISharedFranchiseId
{
FranchiseBilling Billing { get; set; }
FranchiseCompliance Compliance { get; set; }
FranchiseLeadAllocation LeadAllocation { get; set; }
FranchiseMessaging Messaging { get; set; }
FranchiseMiscellaneous Miscellaneous { get; set; }
FranchiseSignup Signup { get; set; }
}
So each of the models sharing the primary key have these properties (the annoying bit):
public class FranchiseBilling/Compliance/etc : ISharedFranchiseId
{
// Properties implemented on this model
#region Navigations to other entities sharing primary key
public virtual FranchiseBilling Billing { get; set; }
public virtual FranchiseCompliance Compliance { get; set; }
public virtual FranchiseLeadAllocation LeadAllocation { get; set; }
public virtual FranchiseMessaging Messaging { get; set; }
public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
public virtual FranchiseSignup Signup { get; set; }
#endregion
}
And configure via Fluent API as follows (the painful bit):
// Franchise = the "primary/parent" model
public class FranchiseMapping : EntityTypeConfiguration<Franchise>
{
public FranchiseMapping()
{
HasRequired(x => x.Billing).WithRequiredPrincipal();
HasRequired(x => x.Compliance).WithRequiredPrincipal();
HasRequired(x => x.LeadAllocation).WithRequiredPrincipal();
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal();
HasRequired(x => x.Messaging).WithRequiredPrincipal();
HasRequired(x => x.Signup).WithRequiredPrincipal();
}
}
// Now each "child" model gets link to all the others. We only need links going one way,
// So each model links to the ones listed below.
// This makes it easy to implement an extra child model down the track as we just
// insert the configuration it here and copy from the next one.
public class FranchiseBillingMapping : EntityTypeConfiguration<FranchiseBilling>
{
public FranchiseBillingMapping()
{
Ignore(x => x.Billing);
HasRequired(x => x.Compliance).WithRequiredDependent(x => x.Billing);
HasRequired(x => x.LeadAllocation).WithRequiredPrincipal(x => x.Billing);
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Billing);
HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Billing);
HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Billing);
}
}
public class FranchiseComplianceMapping : EntityTypeConfiguration<FranchiseCompliance>
{
public FranchiseComplianceMapping()
{
Ignore(x => x.Compliance);
HasRequired(x => x.LeadAllocation).WithRequiredPrincipal(x => x.Compliance);
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Compliance);
HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Compliance);
HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Compliance);
}
}
public class FranchiseLeadAllocationMapping : EntityTypeConfiguration<FranchiseLeadAllocation>
{
public FranchiseLeadAllocationMapping()
{
Ignore(x => x.LeadAllocation);
HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.LeadAllocation);
HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.LeadAllocation);
HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.LeadAllocation);
}
}
public class FranchiseeMiscellaneousMapping : EntityTypeConfiguration<FranchiseeMiscellaneous>
{
public FranchiseeMiscellaneousMapping()
{
Ignore(x => x.Miscellaneous);
HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Miscellaneous);
HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Miscellaneous);
}
}
public class FranchiseMessagingMapping : EntityTypeConfiguration<FranchiseMessaging>
{
public FranchiseMessagingMapping()
{
Ignore(x => x.Messaging);
HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Messaging);
}
}
public class FranchiseSignupMapping : EntityTypeConfiguration<FranchiseSignup>
{
public FranchiseSignupMapping()
{
Ignore(x => x.Signup);
}
}
I have a question about defining Foreign Key in EF Code First Fluent API.
I have a scenario like this:
Two class Person and Car. In my scenario Car can have assign Person or not (one or zero relationship).
Code:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public Person Person { get; set; }
public int? PPPPP { get; set; }
}
class TestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Car> Cars { get; set; }
public TestContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
In my sample I want to rename foreign key PersonId to PPPPP. In my mapping I say:
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
But my relationship is one to zero and I'm afraid I do mistake using WithMany method, but EF generate database with proper mappings, and everything works well.
Please say if I'm wrong in my Fluent API code or it's good way to do like now is done.
Thanks for help.
I do not see a problem with the use of fluent API here. If you do not want the collection navigational property(ie: Cars) on the Person class you can use the argument less WithMany method.