I have two classes (Person and Phone) and I didn't figure out how to create a relationship between these classes without create a navigation property.
Is there some way to do that? Or I HAVE to create a navigation property?
Writing in T-SQL, I'm looking for somethink like that:
ALTER TABLE [dbo].[PHONE] WITH NOCHECK
ADD CONSTRAINT [RelationName1] FOREIGN KEY
( [PERSONID] )
REFERENCES [dbo].[PERSON]
( [PERSONID] )
In example below, I don't know how to write this sentence:
modelBuilder.Entity<Person>().HasMany<Phone>().... ?????:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Phone
{
public int PhoneId { get; set; }
public int PersonId { get; set; }
public string PhoneNumber { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Phone> Phones { get; set; }
public MyContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Person>().HasMany<Phone>()...?????
}
}
Thank you!
You do, I think, need a navigation property in at least one direction. If this is causing serialization problems, have you investigated adding attributes to prevent it from being serialized? For example, add the DataContractAttribute on your classes, and add DataMemberAttribute to all the properties that you do want to serialize.
This would give you the following (in which I use attributes, but you could use fluent config instead):
[DataContract]
public class Person
{
[Key]
[DataMember]
public int PersonId { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Phone
{
[Key]
[DataMember]
public int PhoneId { get; set; }
[DataMember]
public int PersonId { get; set; }
// No [DataMember] here.
[ForeignKey("PersonId")]
public Person Person { get; set; }
[DataMember]
public string PhoneNumber { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Phone> Phones { get; set; }
public MyContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// If I had a Phones collection on Person, I could use the other override
// of WithMany.
modelBuilder.Entity<Phone>().HasRequired(q => q.Person).WithMany();
}
}
one way (the only in fact, referring to first comment of your question) I imagine to reach the goal is by overriding the initializer and sending sql command from the seed method of the initializer. Something like:
public class CreateDatabaseIfNotExistsWithSeedData : CreateDatabaseIfNotExists<SomeContext> {
protected override void Seed(SomeContext context) {
base.Seed(context);
context.Database.ExecuteSqlCommand("ALTER TABLE [dbo].[PHONE] WITH NOCHECK ADD CONSTRAINT [RelationName1] FOREIGN KEY ( [PERSONID] ) REFERENCES [dbo].[PERSON] ( [PERSONID] )");
}
}
Based on your comments, following I can suggest
Problems to Serialize those classes using WebApi/JSON :
well, it is a serious problem when you try to serialize any entity having many-to-many (circular) relationship. Even Ignore Circular Reference (as provided by JSON.NET Serializer) doesn't work. The best that you can do is to, instead of serializing your exact entity, you can serialize a Linq .Select list of this entity i.e. Select only scalar properties or ignore circular and not required navigational properties from the Select list and then serialize it.
I want to avoid EF to insert child informations in SaveChanges
What you mean? If you have a foreign key in your table, you got to have a master table with a primary key, to which this foreign key(PersonId) refers to right? That means, you have to have a table PersonMaster or similar with or without data. Entity framework does it simple for you. If you meant, you don't want to insert new data in your PersonMaster, then simply don't add a new Person object to your Phone entity, find the Person (from the same context ) and then add it. EntityFramework will simply use the primary key.
If you tried to say, you want to have a value in your personId column, which doesn't exist anywhere else and still be the foreign key, that's not possible you know that right? Plain SQL.
Related
I have a code-first EF database solution. The following are my model classes:
public class HolidayAllowance
{
private decimal _taken;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public int EmployeeId { get; set; }
public decimal Allowance { get; set; }
public virtual ObservableCollection<Holiday> Holidays { get; set; }
public decimal Taken
{
get { return Holidays?.Sum(x => x.Duration) ?? 0; }
set { _taken = value; }
}
public decimal Remaining => Allowance - Taken;
}
public class Holiday
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime HolidayStartDay { get; set; }
public HolidayType HolidayStartDayType { get; set; }
public decimal Duration { get; set; }
public int HolidayAllowanceId { get; set; }
}
My DbContext:
public class StaffAndInvoiceManagerDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<HolidayAllowance> HolidayAllowances { get; set; }
public DbSet<Note> Notes { get; set; }
public DbSet<Termination> Terminations { get; set; }
}
The method from my DataService that tries to delete a holiday from the holidayAllowance:
public void DeleteHoliday(Holiday selectedHoliday)
{
var allowance = _dbContext
.HolidayAllowances
.FirstOrDefault(x => x.Id == selectedHoliday.HolidayAllowanceId);
allowance?
.Holidays
.Remove(selectedHoliday);
_dbContext.SaveChanges();
}
The method on my ViewModel that calls that:
private void DeleteHoliday()
{
if (!_messageBoxService.AskYesNoQuestion("Delete Holiday?", "Do you want to delete this holiday?")) return;
_staffDataService.DeleteHoliday(SelectedHoliday);
HolidayAllowance.Holidays.Remove(SelectedHoliday);
RaisePropertyChanged(nameof(HolidayAllowance));
}
The problem is, when I try to delete a Holiday from the HolidayAllowance.Holidays collection I get the following error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value.
If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I don't understand why that would be a FK violation? I'm just trying to delete from a collection.
My DB tables that EF is generating.
I can execute the following SQL and it works without throwing a FK exception.
delete from dbo.Holidays
where Id = 2
The Foreign Key its generating looks like this, If I script to Create in SSMS
ALTER TABLE [dbo].[Holidays] WITH CHECK ADD CONSTRAINT [FK_dbo.Holidays_dbo.HolidayAllowances_HolidayAllowanceId]
FOREIGN KEY([HolidayAllowanceId])
REFERENCES [dbo].[HolidayAllowances] ([Id])
GO
ALTER TABLE [dbo].[Holidays] CHECK CONSTRAINT [FK_dbo.Holidays_dbo.HolidayAllowances_HolidayAllowanceId]
GO
I've tried the following in my DBContext after reading #Fals comment below.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
I would have thought EF was doing similar ? Still no luck.
I've found a fix for this, but I'm not sure why it works or if it's the correct way to do it. Anyway,
_dbContext.Entry(selectedHoliday).State = EntityState.Deleted;
_dbContext.SaveChanges();
And the item is deleted. I have no idea why I have to do this and not just delete it from it's parent, but it works.
I am having trouble connecting a many-to-many relationship in EF6 using code first, then creating a pass-through association beyond that.
There are three classes: Person, Tag, and Passing.
Each Person has an optional Bib.
Each Tag has an optional Bib, not unique.
Each Passing has a required TagId.
I want to access all Passings linked to a Person by getting all Tags with the same Bib, then getting all Passings associated with each of those Tags.
I have tried using the DBModelBuilder in my DBContext class but can't get it to work properly, and EF6 seems to try to generate an intermediate table anyways, which seems unnecessary.
public class Person
{
[Key]
public int PersonId { get; set; }
...
public string Bib { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Passing> Passings
}
public class Tag
{
[Key]
public string TagId { get; set; }
public string Bib { get; set; }
public virtual ICollection<Passing> Passings { get; set; }
public virtual Person Person { get; set; }
}
public class Passing
{
[Key]
public int PassingId { get; set; }
...
public string TagId { get; set; }
public virtual Tag Tag { get; set; }
}
IT IS necessary, when you have a * to * multiplicity into a table, it automaticly creates another table that links these, elseway you cannot put an infinite and variable number of foraign key in one of your table
Entity Framework uses navigation properties to represent database relationships. If you don't want an additional table, what you have here is not a database relationship since keys aren't involved.
You should be able to use some kind of function (or extension function) to get what you want:
IQueryable<Passing> PersonPassings(YourContext db, Person p)
{
return db.Passings.Where(pa => pa.Tag.Bib == p.Bib);
}
On the other hand, if you want to create a proper relationship, you'll need an intermediate Bibs table to connect Person and Tag.
Time for a dumb question. I think the database design is screwy, but there isn't much I can do about that part of it. I have a table in the database "Table1" and then "Table2" which is essentially an extension of Table1 (I also have Table3, Table4, etc). My problem is that Table2 has it's own unique key, even though it's a one for one relationship. Then Table2Component uses Table2Id as it's foreign key. However, when I try to use that in my code I think it's pointing to Table1Id. I get the error message:
System.Data.Entity.Edm.EdmAssociationConstraint: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'Table2Id' on entity 'Table2Component' does not match the type of property 'Table1Id' on entity 'Table2' in the referential constraint 'Table2Component_Table2'.
Here is the code
[Table("Table1")]
public abstract class Table1
{
[Key]
[Column("table1_id")]
public string Table1Id { get; set; }
[Column("name")]
public string Name { get; set; }
[Column("type_cd")]
public string TypeCode { get; set; }
}
[Table("Table2")]
public class Table2 : Table1
{
[Key]
[Column("table2_id")]
public int Table2Id { get; set; }
[ForeignKey("Table1Id")]
public virtual Table1 Table1 { get; set; }
// this table also has a table1_id column
// but I guess I don't need it here, correct?
[Column("column1")]
public string Column1 { get; set; }
public virtual ICollection<Table2Component> Table2Components { get; set; }
}
[Table("Table2Component")]
public class Table2Component : ISubItem
{
[Key]
[Column("table2_component_id")]
public int Table2ComponentId { get; set; }
[Column("table2_id")]
public int Table2Id { get; set; }
[Column("description")]
public string Description { get; set; }
public bool Required { get { return true; } }
[ForeignKey("Table2Id")]
public virtual Table2 Table2 { get; set; }
}
Any suggestions? Should I be more forceful in trying to get the database changed?
Started as comment.... finish as simple answer, since no one else jumped in.
Search for Entity Framework 1:1 relationship eg https://stackoverflow.com/a/14997417/1347784 the restriction is both tables must have the same foreign key when using 1:1
No not necessarily better database design. It is Just the why the EF team built the framework. Ive learnt to live with the restrictions. In code first scenario, no big deal. Try the powertool to reverse engineer the alternative approach when you start with the DB. EF will use 1:M even though you may see it as 1:1. Also OK in my view.
I'm having trouble with reverse navigation on one of my entities.
I have the following two objects:
public class Candidate
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CandidateId { get; set; }
....
// Reverse navigation
public virtual CandidateData Data { get; set; }
...
// Foreign keys
....
}
public class CandidateData
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CandidateDataId { get; set; }
[Required]
public long CandidateId { get; set; }
// Foreign keys
[ForeignKey("CandidateId")]
public virtual Candidate Candidate { get; set; }
}
Now my foreign key navigation on the CandidateData object works fine. I am having trouble getting the reverse navigation for the candidate object to work (if that's even possible).
This is my OnModelCreating function:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Candidate>()
.HasOptional(obj => obj.Data)
.WithOptionalPrincipal();
base.OnModelCreating(modelBuilder);
}
It's close to working except in the database I get two columns that link to the CandidateId. I get the one I from the POCO object the I get another column Candidate_CandidateId I assume was created by the modelBuilder.
I am quiet lost at the moment. Can someone please shed some light on what's going on?
The One to One problem....
The issue is EF and CODE First, when 1:1 , for the dependent to have a Primary key that refers to the principal. ALthough you can define a DB otherwise and indeed with a DB you can even have OPTIONAL FK on the Primary. EF makes this restriction in Code first. Fair Enough I think...
TRy this instead: IS have added a few opinions on the way which you may ignore if you disagree:-)
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
namespace EF_DEMO
{
class FK121
{
public static void ENTRYfk121(string[] args)
{
var ctx = new Context121();
ctx.Database.Create();
System.Console.ReadKey();
}
}
public class Candidate
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]// best in Fluent API, In my opinion..
public long CandidateId { get; set; }
// public long CandidateDataId { get; set; }// DONT TRY THIS... Although DB will support EF cant deal with 1:1 and both as FKs
public virtual CandidateData Data { get; set; } // Reverse navigation
}
public class CandidateData
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] // best in Fluent API as it is EF/DB related
public long CandidateDataId { get; set; } // is also a Foreign with EF and 1:1 when this is dependent
// [Required]
// public long CandidateId { get; set; } // dont need this... PK is the FK to Principal in 1:1
public virtual Candidate Candidate { get; set; } // yes we need this
}
public class Context121 : DbContext
{
static Context121()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context121>());
}
public Context121()
: base("Name=Demo") { }
public DbSet<Candidate> Candidates { get; set; }
public DbSet<CandidateData> CandidateDatas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Candidate>();
modelBuilder.Entity<CandidateData>()
.HasRequired(q => q.Candidate)
.WithOptional(p=>p.Data) // this would be blank if reverse validation wasnt used, but here it is used
.Map(t => t.MapKey("CandidateId")); // Only use MAP when the Foreign Key Attributes NOT annotated as attributes
}
}
}
I think that the foreign key should be created as:
.Map(t => t.MapKey("CandidateDataId")) because thsi foreign key will be placed in Candidate table...
Waht do you think?
Hi I try use Many to Many relationship with EF Fluent API. I have 2 POCO classes.
public class Project
{
public int ProjectId { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Project()
{
Authors = new List<Author>();
}
}
public class Author
{
public int AuthorId { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Author()
{
Projects = new List<Project>();
}
}
And I map many to many relationship with this part of code:
////MANY TO MANY
modelBuilder.Entity<Project>()
.HasMany<Author>(a => a.Authors)
.WithMany(p => p.Projects)
.Map(m =>
{
m.ToTable("ProjectAuthors");
m.MapLeftKey("ProjectId");
m.MapRightKey("AuthorId");
});
This created table ProjectsAuthors in DB. It is my first attempt with this case of relationship mapping.
If I omitted this mapping it created table AuthorProject with similar schema. It is correct bevahior?
By trial and error I found the following. Given two classes...
public class AClass
{
public int Id { get; set; }
public ICollection<BClass> BClasses { get; set; }
}
public class BClass
{
public int Id { get; set; }
public ICollection<AClass> AClasses { get; set; }
}
...and no Fluent mapping and a DbContext like this...
public class MyContext : DbContext
{
public DbSet<AClass> AClasses { get; set; }
public DbSet<BClass> BClasses { get; set; }
}
...the name of the created join table is BClassAClasses. If I change the order of the sets...
public class MyContext : DbContext
{
public DbSet<BClass> BClasses { get; set; }
public DbSet<AClass> AClasses { get; set; }
}
...the name of the created join table changes to AClassBClasses and the order of the key columns in the table changes as well. So, the name of the join table and the order of the key columns seems to depend on the order in which the entity classes are "loaded" into the model - which can be the order of the DbSet declarations or another order if more relationship are involved - for example some other entity refering to AClass.
In the end, it doesn't matter at all, because such a many-to-many relationship is "symmetric". If you want to have your own name of the join table, you can specify it in Fluent API as you already did.
So, to your question: Yes, naming the join table AuthorProjects is correct behaviour. If the name had been ProjectAuthors it would be correct behaviour as well though.