Entity Framework Core multiple relationships to same table - entity-framework

I have a problem with two references to the same table with different columns:
public class MainApplicationContext : DbContext
{
public MainApplicationContext(MainSqlDbContext mainSqlDbContext)
{
MainSqlDbContext = mainSqlDbContext;
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public DbSet<Organisation> Organisations { get; set; }
public DbSet<OrganisationContact> OrganisationContacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Organisation>()
.HasKey(t => new { t.OrgId, t.OrgType, });
modelBuilder.Entity<OrganisationContact>().Property(p => p.OcsId).HasValueGenerator<SequenceNumberValueGenerator>().ValueGeneratedOnAdd();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(MainSqlDbContext.Database.GetDbConnection());
base.OnConfiguring(optionsBuilder);
}
private MainSqlDbContext MainSqlDbContext;
}
[SequenceNameAttribute("ORGANISATIONCONTACTS", "web")]
[Table("ORGANISATIONCONTACTS", Schema = "dbo")]
[Serializable]
public partial class OrganisationContact
{
[Column("OCS_ACTIVE")]
[MaxLength(1)]
public string OcsActive { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
[Column("OCS_ID")]
public int OcsId { get; set; }
[Column("OCS_NAME")]
[MaxLength(255)]
public string OcsName { get; set; }
[Column("OCS_ORGANISATION_KEY")]
[RelationshipTableAttribue("ORGANISATIONS", "dbo")]
//Relationships
public int OcsOrganisationKey { get; set; }
[ForeignKey("OcsOrganisationKey")]
public Organisation Organisation { get; set; }
[Column("OCS_TYPE")]
[MaxLength(20)]
[RelationshipTableAttribue("ORGANISATIONS", "dbo")]
// Relationships
public string OcsType { get; set; }
[ForeignKey("OCS_TYPE")]
public Organisation Organisation1 { get; set; }
public OrganisationContact()
{
}
}
[SequenceNameAttribute("ORGANISATIONS", "web")]
[Table("ORGANISATIONS", Schema = "dbo")]
[Serializable]
public partial class Organisation
{
[Column("ORG_EMAIL")]
[MaxLength(255)]
public string OrgEmail { get; set; }
[Range(0, int.MaxValue)]
[Column("ORG_ID")]
public int OrgId { get; set; }
[Required]
[Column("ORG_NAME")]
[MaxLength(255)]
public string OrgName { get; set; }
[Required]
[Column("ORG_TYPE")]
[MaxLength(20)]
public string OrgType { get; set; }
[InverseProperty("Organisation")]
public ICollection<OrganisationContact> OrganisationContacts { get; set; }
[InverseProperty("Organisation1")]
public ICollection<OrganisationContact> ORGANISATIONCONTACTS1 { get; set; }
public Organisation()
{
this.OrganisationContacts = new HashSet<OrganisationContact>();
this.ORGANISATIONCONTACTS1 = new HashSet<OrganisationContact>();
}
}
I get this error:
System.InvalidOperationException: 'The property 'OCS_TYPE' cannot be added to the type 'OrganisationContact' because there was no property type specified and there is no corresponding CLR property or field. To add a shadow state property the property type must be specified.

The core issue here is that you define a composite primary key in table Organisation but you try to use single fields as foreign keys in table OrganisationContact.
If the primary key of the referenced table is composite, the foreign keys referencing it must be composite, as well, consisting of fields of the same number and type:
[Table("ORGANISATIONCONTACTS", Schema = "dbo")]
public partial class OrganisationContact
{
// irrelevant declarations omitted for brevity...
[Column("OCS_ORGANISATION_ORG_ID")]
public int Organisation_OrgId { get; set; }
[Column("OCS_ORGANISATION_ORG_TYPE")]
public string Organisation_OrgType { get; set; }
[ForeignKey(nameof(Organisation_OrgId) + "," + nameof(Organisation_OrgType))]
public Organisation Organisation { get; set; }
[Column("OCS_ORGANISATION1_ORG_ID")]
public int Organisation1_OrgId { get; set; }
[Column("OCS_ORGANISATION1_ORG_TYPE")]
public string Organisation1_OrgType { get; set; }
[ForeignKey(nameof(Organisation1_OrgId) + "," + nameof(Organisation1_OrgType))]
public Organisation Organisation1 { get; set; }
}
[Table("ORGANISATIONS", Schema = "dbo")]
public partial class Organisation
{
// irrelevant declarations omitted for brevity...
[InverseProperty(nameof(OrganisationContact.Organisation))]
public ICollection<OrganisationContact> OrganisationContacts { get; set; }
[InverseProperty(nameof(OrganisationContact.Organisation1))]
public ICollection<OrganisationContact> ORGANISATIONCONTACTS1 { get; set; }
}
Some suggestions:
Please post MCV code. There are some exotic attributes (like RelationshipTableAttribue) and unknown type references (MainSqlDbContext) which has nothing to do with the problem but makes more cumbersome to review the issue.
Try to avoid hardcoded strings as much as possible. The nameof operator has been available for quite a while (since C# 6.0).
The preferred way to configure your DB mappings is fluent API in EF Core. Data annotation attributes are pretty limited in functionality. (E.g. you cannot define a composite primary key using attributes in EF Core.)

Related

The entity type 'Program' requires a primary key to be defined

I am trying to make a simple website that tracks students, programs, and classes. I've created the entities and I'm getting an error when trying to add the migration.
"The entity type 'Program' requires a primary key to be defined."
I have tried using the [Key] attribute and there is an Id field. The other table was created just fine. What else should I try?
Here is the problem class:
public class Program
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool UseRanks { get; set; }
}
Here is another table that I had no problems creating a migration for:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public string WorkPhone { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public DateTime BirthDate { get; set; }
}
Here is what is in my ApplicationDbContext class:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
//public DbSet<Attendance> Attendances { get; set; }
public DbSet<Person> People { get; set; }
public DbSet<Bill> Bills { get; set; }
//public DbSet<Session> Sessions { get; set; }
public DbSet<Program> Programs { get; set; }
}
I've commented out the other entities because I was trying to add them one at a time. Trying to add a migration with all the entities resulted in the same error with the same specific class.
Complete shot in the dark, but based on the name of this class, I'm guessing you're referencing the wrong Program. Make sure that your DbSet<Program> is actually using your Program entity and not something like the Program class used at the console app level. You'll likely need to explicitly use the namespace, i.e. DbSet<MyApp.Models.Program>.
You might also consider changing the name of the class to remove any chance of ambiguity. There's some class names that are just going to wreck havoc trying to use them because they'll conflict with framework stuff constantly. It's usually more hassle than it's worth just to have that particular name. Program is one of those.
You can try to use this way:
public class Program
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool UseRanks { get; set; }
}
Adding [Key] attribute to the Id property.
In the file ApplicationDbContext.cs, you can override OnModelCreating method:
public DbSet<Program> Programs { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Program>().ToTable("Programs").HasKey(x => x.Id);
}

Entity Framework 4.3.1 how to create associations

my code like below
public class User
{
public int ID { get; set; }
public int BillingAddressID { get; set; }
public Address BillingAddress { get; set; }
public IList<Shipment> Shipments { get; set; }
}
public class Address
{
public int ID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
public class Shipment
{
public int ID { get; set; }
public string State { get; set; }
public int DeliveryAddressID { get; set; }
public Address DeliveryAddress { get; set; }
public User ShipUser { get; set; }
//[ForeignKey("ShipUser")]
public int ShipUserID { get; set; }
//public int UserId { get; set; }
}
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Shipment> Shipments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Shipment>().HasRequired(u => u.ShipUser)
.WithMany(d => d.Shipments)
.HasForeignKey(c => c.ShipUserID)
.WillCascadeOnDelete(false);
}
}
if i remove the override method,i will get an error "SqlException: Introducing FOREIGN KEY constraint 'FK_Shipments_Users_ShipUserID' on table 'Shipments' 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."
if i remove ShipUserID in Shipment Class,it will work ok,when i see the table that is created by ef,i found a column named Shipment_UserID in table Shipment.I don`t know why.
if rename the class indenty key to UserID,it also work ok.
I try it anyway,but I don`t know the reason, I need some books about EF associations.
If you don't have mapping specified without cascadeDelete=false for one relationship it will create multiple cascade paths if you have tow relationships to user from Shipment.
By convention you can use public
Public User ShipUser { get; set; }
public int ShipUserID { get; set; }
it will use ShipUserID as foreign key by convention.
If you remove ShipUserID Ef need to create his own foreign key to keep the relationship . that is your ' Shipment_UserID'
rename the class indenty key to UserID I don't understand what you meant.
Here is a good tutorial to start with

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table

How do I code an optional one-to-one relationship in EF 4.1 code first with lazy loading and the same primary key on both tables?

I'm working with an application and data structure built upon ASP/ADO.NET and I'm converting part of it to ASP.NET MVC. In the data structure, there exists a "optional one-to-one" relationship, where both tables use the same primary key, and name. Basically this table can be considered an "optional extension" of the primary table. Here are samples of the model:
public class ZoneMedia
{
public int ZoneMediaID { get; set; }
public string MediaName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public virtual ZoneMediaText MediaText { get; set; }
}
public class ZoneMediaText
{
public int ZoneMediaID { get; set; }
public string Text { get; set; }
public int Color { get; set; }
}
Obviously, EF 4.1 code first has an issue mapping this automatically. So I realize I must specify the mapping explicitly. I tried this:
modelBuilder.Entity<ZoneMedia>()
.HasOptional(zm => zm.ZoneMediaText);
modelBuilder.Entity<ZoneMediaText>()
.HasRequired(zmt => zmt.ZoneMedia)
.WithRequiredDependent(zm => zm.ZoneMediaText)
.Map(m => m.MapKey("ZoneMediaID"));
But it is still giving me an exception about the name of the primary key.
Schema specified is not valid. Errors:
(199,6) : error 0019: Each property name in a type must be unique. Property name 'ZoneMediaID' was already defined.
I'm a little stumped. I need to adapt to this non-conventional structure I realize in EF 4.1 it would be much easier to just add a unique PK to the optional relation and hold the foreign key relationship in the primary table, but I can't change the database layout. Any advice would be appreciated.
I hope i understood well.
This works for me:
public class ZoneMedia
{
public int ZoneMediaID { get; set; }
public string MediaName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public virtual ZoneMediaText MediaText { get; set; }
}
public class ZoneMediaText
{
public int ZoneMediaID { get; set; }
public string Text { get; set; }
public int Color { get; set; }
public virtual ZoneMedia ZoneMedia { get; set; }
}
public class TestEFDbContext : DbContext
{
public DbSet<ZoneMedia> ZoneMedia { get; set; }
public DbSet<ZoneMediaText> ZoneMediaText { get; set; }
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ZoneMedia>()
.HasOptional(zm => zm.MediaText);
modelBuilder.Entity<ZoneMediaText>()
.HasKey(zmt => zmt.ZoneMediaID);
modelBuilder.Entity<ZoneMediaText>()
.HasRequired(zmt => zmt.ZoneMedia)
.WithRequiredDependent(zm => zm.MediaText);
base.OnModelCreating(modelBuilder);
}
}
class Program
{
static void Main (string[] args)
{
var dbcontext = new TestEFDbContext();
var medias = dbcontext.ZoneMedia.ToList();
}
}
This Correctly create a FK_ZoneMediaTexts_ZoneMedias_ZoneMediaID in ZomeMediaTexts table, and the Foreign Key is the Primary Key.
EDIT: maybe it's worth pointing out that I'm using EF 4.3.0

Entity Framework Code First Many to Many Setup For Existing Tables

I have the following tables Essence, EssenseSet, and Essense2EssenceSet
Essense2EssenceSet is the linking table that creates the M:M relationship.
I've been unable to get the M:M relationship working though in EF code first though.
Here's my code:
[Table("Essence", Schema = "Com")]
public class Essence
{
public int EssenceID { get; set; }
public string Name { get; set; }
public int EssenceTypeID { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<EssenceSet> EssenceSets { get; set; }
public virtual EssenceType EssenceType { get; set; }
}
[Table("EssenceSet", Schema = "Com")]
public class EssenceSet
{
public int EssenceSetID { get; set; }
public int EssenceMakerID { get; set; }
public string Name { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<Essence> Essences { get; set; }
}
[Table("Essence2EssenceSet", Schema = "Com")]
public class Essence2EssenceSet
{
//(PK / FK)
[Key] [Column(Order = 0)] [ForeignKey("Essence")] public int EssenceID { get; set; }
[Key] [Column(Order = 1)] [ForeignKey("EssenceSet")] public int EssenceSetID { get; set; }
//Navigation
public virtual Essence Essence { get; set; }
public virtual EssenceSet EssenceSet { get; set; }
}
public class EssenceContext : DbContext
{
public DbSet<Essence> Essences { get; set; }
public DbSet<EssenceSet> EssenceSets { get; set; }
public DbSet<Essence2EssenceSet> Essence2EssenceSets { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});
}
}
This is the code I'm trying to run:
Essence e = new Essence();
e.EssenceTypeID = (int)(double)dr[1];
e.Name = dr[2].ToString();
e.DescLong = dr[3].ToString();
//Get Essence Set
int setID = (int)(double)dr[0];
var set = ctx.EssenceSets.Find(setID);
e.EssenceSets = new HashSet<EssenceSet>();
e.EssenceSets.Add(set);
ctx.Essences.Add(e);
ctx.SaveChanges();
And here's the error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm not able to find the problem. I'd greatly appreciate help setting this up right.
Thanks!
Remove your Essence2EssenceSet model class. If junction table contains only keys of related entities participating in many-to-many relations it is not needed to map it as entity. Also make sure that your fluent mapping of many-to-many relations specifies schema for table:
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet", "Com");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});