class Event{
int? EventID{get;set;}
int? FirstParticipantID{get;set;}
Participant FirstParticipant{get;set;}
int? SecondParticipantID{get;set;}
Participant SecondParticipant{get;set;}
int? CreatedByID{get;set;}
User CreatedBy{get;set;}
}
class Participant{
int? ParticipantID{get;set;}
List<Event> Events{get;set;}
int? CreatedByID{get;set;}
User CreatedBy{get;set;}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<Event>().HasRequired(m => m.FirstParticipant).WithMany(m => m.Events).HasForeignKey(m => m.FirstParticipantID);
modelBuilder.Entity<Event>().HasRequired(m => m.SecondParticipant).WithMany(m => m.Events).HasForeignKey(m => m.SecondParticipantID);
modelBuilder.Entity<Event>().HasRequired(m => m.CreatedBy);
modelBuilder.Entity<Participant>().HasRequired(m => m.CreatedBy);
}
It seems very clear to me, but EF (and sql) keeps complaining, no matter what moves I make to the hasmanny/hasrequired stuff. I can't even find google help cause I don't know the name of what I'm trying to implement (double one to one???!!!)
The idea is that an Event must have 2 not null Participants (first & second only, not many) and that each Participant may have many Events
Thanks
There are multiple problems in your code:
PK cannot be nullable
FK cannot be nullable if you want to define the relation as Required
When you define two relations with Participant you need two navigation properties - you cannot map two relations into single Events property
You didn't complete your mapping of CreatedBy in both Event and Participant
Try this:
public class Event{
public int EventID {get;set;}
public int FirstParticipantID{get;set;}
public Participant FirstParticipant{get;set;}
public int SecondParticipantID{get;set;}
public Participant SecondParticipant{get;set;}
public int CreatedByID{get;set;}
public User CreatedBy{get;set;}
}
public class Participant{
public int ParticipantID {get;set;}
public int CreatedByID {get;set;}
public User CreatedBy {get;set;}
public ICollection<Event> FristEvents { get; set; }
public ICollection<Event> SecondEvents { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<Event>()
.HasRequired(m => m.FirstParticipant)
.WithMany(m => m.FirstEvents)
.HasForeignKey(m => m.FirstParticipantID);
modelBuilder.Entity<Event>()
.HasRequired(m => m.SecondParticipant)
.WithMany(m => m.SecondEvents)
.HasForeignKey(m => m.SecondParticipantID);
modelBuilder.Entity<Event>()
.HasRequired(m => m.CreatedBy)
.WithMany()
.HasForeignKey(m => m.CreatedByID);
modelBuilder.Entity<Participant>()
.HasRequired(m => m.CreatedBy)
.WithMany()
.HasForeignKey(m => m.CreatedByID);
}
Related
I am using EF Core 6.0 with MySQL (Pomelo.EntityFrameworkCore.MySql v6.0)
I am setting up my database via code first. Here are my 2 models (simplified):
public class Store : BaseEntity
{
public string Name { get; set; }
public virtual User? Owner { get; set; }
public int? OwnerId { get; set; }
}
public class User : BaseEntity
{
public string? Name { get; set; }
public virtual Store? Store { get; set; }
}
For both, I have
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
as the primary key (from BaseEntity).
I also have Lazy Loading enabled here:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
and in the Program.cs:
builder.Services.AddDbContext<AppDbContext>(opt => opt
.UseLazyLoadingProxies()
.ConfigureWarnings(warning => warning.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.EnableSensitiveDataLogging()
.UseMySql(
Globals.DB_CONNECTION_STRING, new MySqlServerVersion(Globals.MYSQL_SERVER_VERSION),
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
));
Now, to the problem - I am trying to get a store from the database, using the following code:
Store? store = await dbContext.Stores
.Include(x => x.Owner)
.FirstOrDefaultAsync(x => x.Owner.Id == ownerId && x.Id == storeId);
I am getting the store details, but the Owner object and OwnerId is null. I can see the data in the database (e.g. I see OwnerId is set up for this specific store), but in the code, it is null.
I read on SO that Pomelo has some issues with setting up navigation properties, so I set it manually in OnModelCreating as follows:
modelBuilder.Entity<User>()
.HasOne(x => x.Store)
.WithOne(x => x.Owner)
;
But that didn't do the trick.
The same configuration works perfectly with MSSQL.
Any thoughts?
Thanks
Call HasForeignKey after WithOne and point to OwnerId.
I have a users table and a products table
and a many-to-many relationship between them in table UsersProducts.
Each user can request to get more products, And I decided I need to save the date when I gave the user the products
public class UserProduct
{
public int UserId { get; set; }
public User User { get; set; }
public Product Product { get; set; }
public int ProductId { get; set; }
public DateTime DateAssigned { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProduct>()
.HasKey(bc => new { UserId = bc.UserId, ProductId = bc.ProductId });
modelBuilder.Entity<UserProduct>()
.HasOne(bc => bc.User)
.WithMany(b => b.Products)
.HasForeignKey(bc => bc.UserId);
modelBuilder.Entity<UserProduct>()
.HasOne(bc => bc.Product)
.WithMany(c => c.Owners)
.HasForeignKey(bc => bc.ProductId);
base.OnModelCreating(modelBuilder);
}
When I try to access (UsersProducts.Where(...)) it I get "unknown column in field list" in regards to DateAssigned column.
This is the code EF generated in the migration
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "DateAssigned",
table: "UsersProducts",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DateAssigned",
table: "UsersProducts");
}
It seems the column exists in the table, So why is this happening?
Try to add it in mapping
.Property(t => t.DateAssigned).HasColumnName("DateAssigned")
I have users which have many roles
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public List<Role> Roles {get;set;}
}
public class Roles
{
public int Id {get;set;}
public string Key{get;set;}
}
public class UserRoles
{
public int UserId {get;set;}
public int RoleId {get;set;}
}
what I try to achieve is getting a user with all its roles in one query, but so far I failed.
For Mapping I use a custom Conventionbased mapper (I can provide the code, but it's rather big)
I tried FetchOneToMany and I tried Fetch as described here
https://github.com/schotime/NPoco/wiki/One-to-Many-Query-Helpers
https://github.com/schotime/NPoco/wiki/Version-3
But Roles is always empty.
Role and User by itself are mapped correctly and I did try to specify the relation like
For<User>().Columns(x =>
{
x.Many(c => c.Roles);
x.Column(c => c.Roles).ComplexMapping();
}, true);
Again it didn't help, roles is empty.
I have no idea what I'm missing.
Any ideas?
ComplexMapping and relationship mapping(1-to-n, n-to-n) are two different things.
ComplexMapping is for mapping nested objects for data that usually resides in the same table where there is a one-to-one relationship. For something like this:
public class Client
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public Client()
{
Address = new Address();
}
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Telephone { get; set; }
public string Country{ get; set; }
}
If you're using a convention-based mapper your override would look something like this:
For<Client>().Columns(x =>
{
x.Column(y => y.Address).ComplexMapping();
});
One thing to watch for when using a convention-based mapper; you have to enable ComplexMapping in your scanner with the following code:
scanner.Columns.ComplexPropertiesWhere(y => ColumnInfo.FromMemberInfo(y).ComplexMapping);
Otherwise ComplexMapping() calls in your overrides will simply be ignored.
One-to-Many mapping would work like this (see NPoco on Github for more):
For<One>()
.TableName("Ones")
.PrimaryKey(x => x.OneId)
.Columns(x =>
{
x.Column(y => y.OneId);
x.Column(y => y.Name);
x.Many(y => y.Items).WithName("OneId").Reference(y => y.OneId);
}, true);
For<Many>()
.TableName("Manys")
.PrimaryKey(x => x.ManyId)
.Columns(x =>
{
x.Column(y => y.ManyId);
x.Column(y => y.Value);
x.Column(y => y.Currency);
x.Column(y => y.OneId);
x.Column(y => y.One).WithName("OneId").Reference(y => y.OneId, ReferenceType.OneToOne);
}, true);
I'm trying to model a self-referencing many to many in EF CodeFirst with a polymorphic table structure. I'm using the October 2011 CTP which supports navigation properties on derived types (which works well in other tests I've done).
The problem:
When I set up this particular many to many relationship in the base (abstract) table's mapping and try to get related records, I get a SQL query with hundreds of K of unions and joins...just the time taken to generate the SQL statement is 30 seconds, compared to bare milliseconds to execute it. However, it does return appropriate results. When I change the many to many to exist between two derived objects, the query produced is perfect...but I can't map the same relating M2M table again for other derived objects without being informed that the joining table has "already been mapped".
Specifics:
An existing database structure has a base table--Party--which is joined 1...1 or 0 with Customer, Vendor, User, and Department (each a type of Party).
Parties are related to each other via an existing join table PartyRelationship (ID, InternalPartyID, ExternalPartyID). By convention, InternalPartyID contains a User's PartyID and ExternalPartyID contains the PartyID of the Customer, Vendor, or Department with which they are associated.
Trying to use EF CodeFirst in a new project (WCF DataServices), I have created the Party class as:
public abstract class Party
{
public Party()
{
this.Addresses = new List<Address>();
this.PhoneNumbers = new List<PhoneNumber>();
this.InternalRelatedParties = new List<Party>();
this.ExternalRelatedParties = new List<Party>();
}
public int PartyID { get; set; }
public short Active { get; set; }
//other fields common to Parties
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
public virtual ICollection<Party> InternalRelatedParties { get; set; }
public virtual ICollection<Party> ExternalRelatedParties { get; set; }
}
Then, using TPT inheritance, Customer, Vendor, Department and User are similar to:
public class Customer : Party
{
public string TermsCode { get; set; }
public string DefaultFundsCode { get; set; }
//etc
}
public class User : Party
{
public string EmployeeNumber { get; set; }
public string LoginName { get; set; }
//etc
}
The joining table:
public class PartyRelationship
{
public int PartyRelationshipID { get; set; }
public int InternalPartyID { get; set; }
public int ExternalPartyID { get; set; }
//certain other fields specific to the relationship
}
Mappings:
public class PartyMap : EntityTypeConfiguration<Party>
{
public PartyMap()
{
// Primary Key
this.HasKey(t => t.PartyID);
// Properties
this.ToTable("Party");
this.Property(t => t.PartyID).HasColumnName("PartyID");
this.Property(t => t.Active).HasColumnName("Active");
//etc
// Relationships
this.HasMany(p => p.InternalRelatedParties)
.WithMany(rp => rp.ExternalRelatedParties)
.Map(p => p.ToTable("PartyRelationship")
.MapLeftKey("ExternalPartyID")
.MapRightKey("InternalPartyID"));
}
}
public class PartyRelationshipMap : EntityTypeConfiguration<PartyRelationship>
{
public PartyRelationshipMap()
{
// Primary Key
this.HasKey(t => t.PartyRelationshipID);
// Properties
// Table & Column Mappings
//this.ToTable("PartyRelationship"); // Commented out to prevent double-mapping
this.Property(t => t.PartyRelationshipID).HasColumnName("PartyRelationshipID");
this.Property(t => t.InternalPartyID).HasColumnName("InternalPartyID");
this.Property(t => t.ExternalPartyID).HasColumnName("ExternalPartyID");
this.Property(t => t.CreateTime).HasColumnName("CreateTime");
this.Property(t => t.CreateByID).HasColumnName("CreateByID");
this.Property(t => t.ChangeTime).HasColumnName("ChangeTime");
this.Property(t => t.ChangeByID).HasColumnName("ChangeByID");
}
}
Context:
public class MyDBContext : DbContext
{
public MyDBContext()
: base("name=MyDBName")
{
Database.SetInitializer<MyDBContext>(null);
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Party> Parties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Configurations.Add(new PartyMap());
modelBuilder.Configurations.Add(new PartyRelationshipMap());
}
}
A URL such as http://localhost:29004/Services/MyDataService.svc/Parties(142173)/SAData.Customer/InternalRelatedParties eventually returns correct oData but takes 30 seconds to produce an enormous SQL statement (189K) that executes in 600 ms.
I've also tried mapping the PartyRelationship table with a bidirectional one to many (both to Party as the "one" table), but with a similar outcome.
Do I need separate join tables for Customer-User, Vendor-User, and Department-User? Should I look at vertical table splitting or database views that separates PartyRelationship into separate logical entities (so I can remap the same table)? Is there another way the EF model should be configured in this scenario?
ANSWERED! (will be posting it shortly. I seriously think it's a bug in EF.)
I have the following set of (code-first) classes which will be the future of the application as we are replacing the old (gen'd from db). The dbcontext, two mappings/configurations, two POCOs, and a base class.
In addition to this in another project entirely is an edmx generated from the database. Up until now, there have not been any issues with the two conflicting. There are several other unrelated sets in the context (not shown,) but it wasn't until I started trying to get the relationship between SalesReps and SalesGroups that I started having issues. Commenting out adding the SalesGroupMapping in OnModelCreating will allow it to run.
The Error:
Schema specified is not valid. Errors:
(19,6) : error 0019: Each property name in a type must be unique. Property name 'PKID_MasterRepGroup' was already defined.
(27,6) : error 0019: Each property name in a type must be unique. Property name 'PKID_MasterRepGroup' was already defined.
The Code:
public class MyDbContext{
public IDbSet<SalesGroup> SalesGroups { get; set; }
public IDbSet<SalesRep> SalesReps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new InsuranceProviderMapping());
modelBuilder.Configurations.Add(new SalesGroupMapping());
modelBuilder.Configurations.Add(new SalesRepMapping());
base.OnModelCreating(modelBuilder);
}
}
class SalesRepMapping : EntityTypeConfiguration<SalesRep>
{
public SalesRepMapping()
{
ToTable("SalesRep");
Property(p => p.Id).HasColumnName("PKID_SalesRep");
Property(p => p.Email).HasColumnName("EMailAddress1");
Property(p => p.Address.Address1).HasColumnName("Address");
Property(p => p.Address.Address2).HasColumnName("Address2");
Property(p => p.Address.City).HasColumnName("City");
Property(p => p.Address.State).HasColumnName("State");
Property(p => p.Address.Zipcode).HasColumnName("Zipcode");
//Property(p => p.SalesGroupId).HasColumnName("FK_MasterRepGroup");
//HasRequired(p => p.SalesGroup).WithMany(c => c.SalesReps).HasForeignKey(b => b.SalesGroupId);
//Note: dropping emailaddress2
}
}
class SalesGroupMapping : EntityTypeConfiguration<SalesGroup>
{
public SalesGroupMapping()
{
ToTable("MasterRepGroup");
Property(p => p.Id).HasColumnName("PKID_MasterRepGroup");
Property(p => p.Name).HasColumnName("CompanyName");
Property(p => p.PrimaryContact.FirstName).HasColumnName("Contact1FirstName");
Property(p => p.PrimaryContact.LastName).HasColumnName("Contact1LastName");
Property(p => p.PrimaryContact.Phone).HasColumnName("Contact1Phone");
Property(p => p.PrimaryContact.Fax).HasColumnName("Contact1Fax");
Property(p => p.PrimaryContact.Email).HasColumnName("Contact1EMail");
Property(p => p.SecondaryContact.FirstName).HasColumnName("Contact2FirstName");
Property(p => p.SecondaryContact.LastName).HasColumnName("Contact2LastName");
Property(p => p.SecondaryContact.Phone).HasColumnName("Contact2Phone");
Property(p => p.SecondaryContact.Fax).HasColumnName("Contact2Fax");
Property(p => p.SecondaryContact.Email).HasColumnName("Contact2EMail");
Property(p => p.Address.Address1).HasColumnName("Address");
Property(p => p.Address.Address2).HasColumnName("Address2");
Property(p => p.Address.City).HasColumnName("City");
Property(p => p.Address.State).HasColumnName("State");
Property(p => p.Address.Zipcode).HasColumnName("Zipcode");
}
}
public class SalesRep: Entity
{
virtual public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Phone]
virtual public string Phone { get; set; }
[Phone]
virtual public string Fax { get; set; }
[Email]
virtual public string Email { get; set; }
virtual public string Notes { get; set; }
virtual public Address Address { get; set; }
//public long SalesGroupId { get; set; }
//[Required]
////[ForeignKey("SalesGroupId")]
//virtual public SalesGroup SalesGroup { get; set; }
}
public class SalesGroup : Entity
{
public SalesGroup()
{
//SalesReps = new List<SalesRep>();
}
[Required]
public string Name { get; set; }
virtual public Address Address { get; set; }
virtual public Contact PrimaryContact { get; set; }
virtual public Contact SecondaryContact { get; set; }
//virtual public ICollection<SalesRep> SalesReps {get; set;}
}
public abstract class Entity : NMTC.Core.DataContracts.IEntity
{
// TODO database columns will eventually be int instead of bigint
public long Id { get; set; }
}
}
Any ideas? I've been banging my head on this one since Friday. If you know of a way I can track down what it's thinking, that would help, too.
Much appreciated.
More Info:
Commenting out the mapping for the Id property alone will allow the model to be created. That won't actually help long-term, though.
The error is a problem with EF.
In the mapping, I map Property(p=>p.Id).HasColumnName("MyId");
In my Contact class, which inherits from my Entity class, there are also IDs.
Because I did not explicitly map these columns, the mapping "trickled down" and was trying to map my Contact.Id properties in the same fashion.
Solutions:
a) Map them explicitly
b) Remove the inheritance so that Contact isn't of type Entity and is instead a ComplexType (doesn't have an ID.)