First of all let me apologize for the poor title, I'm not quite aware of what to call this topic.
Anyways..
I'm trying to create a BankAccount<-->Transactions relation using code first.
Here are my "entity classes"
public class BankAccount : BaseEntity
{
public string Name { get; set; }
public double Balance { get; set; }
public virtual long UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Transaction> FromTransactions { get; set; }
public virtual ICollection<Transaction> ToTransactions { get; set; }
}
and
public class Transaction : BaseEntity
{
public string Message { get; set; }
public double Amount { get; set; }
public virtual long ToId { get; set; }
public virtual BankAccount To { get; set; }
public virtual long FromId { get; set; }
public virtual BankAccount From { get; set; }
}
As you can see I would like a transaction to be able to have a "From" BankAccount and a "To" BankAccount for making it easy to navigate from a transaction to the belonging BankAccount(s). Also the BankAccount has two collections of transactions, one "From" and one "To", and that's also to make it easy to navigate between the objects.
The thing is that as soon as I run Update-Database it fails with:
Conflicting changes to the role 'BankAccount_ToTransactions_Source' of the relationship 'OpenFridge.Api.Data.BankAccount_ToTransactions' have been detected
I also have added the two following EntityTypeConfigurations:
public class BankAccountEntityTypeConfiguration : BaseEntityTypeConfiguration<BankAccount>
{
public BankAccountEntityTypeConfiguration()
{
ToTable("BankAccounts");
HasRequired(e => e.User)
.WithMany(e => e.BankAccounts);
Property(e => e.Balance)
.IsRequired();
Property(e => e.Name)
.IsRequired();
HasMany(e => e.ToTransactions)
.WithRequired(e => e.To).WillCascadeOnDelete(false);
HasMany(e => e.FromTransactions)
.WithRequired(e => e.From).WillCascadeOnDelete(false);
}
}
public class TransactionEntityTypeConfiguration : BaseEntityTypeConfiguration<Transaction>
{
public TransactionEntityTypeConfiguration()
{
ToTable("Transactions");
Property(e => e.Amount)
.IsRequired();
HasRequired(e => e.From);
HasRequired(e => e.To);
}
}
Any idea of how to create this kind of relation in a proper way? I might simply be missing something in my database-design as well..
Br,
Inx
For what it's worth, a quick fix for update-database that fails (if you're still in the design phase) is to delete the database and run update-database again to build from scratch. Occasionally, errors have to do with sequence of events as you change your model.
Another check you can make is to run update-database -script and run the resultant SQL in the database manually.
The final quick fix I can offer is to delete the offending relationship in the database.
To help you in the future it may help to use data annotations in your model [Required] [Key] [ForeignKey("ForeignKeyId")] can be helpful in specifying how everything connects and reduce code in your configurations to just the relationships.
You may need to specify the names of your foreign keys in the Transaction entity like so:
public class Transaction : BaseEntity
{
public string Message { get; set; }
public double Amount { get; set; }
public virtual long ToId { get; set; }
[ForeignKey("ToId")]
public virtual BankAccount To { get; set; }
public virtual long FromId { get; set; }
[ForeignKey("FromId")]
public virtual BankAccount From { get; set; }
}
Related
I have three entities:
ApplicationUser:
public class ApplicationUser : IdentityUser<Guid>
{
// more properties
public List<MeetingNotification> MeetingNotifications { get; set; }
}
MeetingNotifications:
public class MeetingNotification
{
public Guid Id { get; set; }
public Guid MeetingId { get; set; }
public Meeting Meeting { get; set; }
public Guid SummonedUserId { get; set; }
public ApplicationUser SummonedUser { get; set; }
}
Meetings:
public class Meeting
{
public Guid Id { get; set; }
// more properties
public Guid OrganizerId { get; set; }
public ApplicationUser Organizer { get; set; }
public List<MeetingNotification> Notifications { get; set; }
}
The relationships are as such:
One ApplicationUser has many MeetingNotifications
One MeetingNotification has one ApplicationUser and one Meeting
One Meeting has many MeetingNotifications
When I try to update-database, I get the error message
Introducing FOREIGN KEY constraint 'FK_MeetingNotifications_Meetings_MeetingId' on table 'MeetingNotifications' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
This error is my arch nemesis, as I struggle to understand were the error lies. Is it in Meeting or in MeetingNotification. Or somewhere else?
I have tried these three definitions:
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.HasForeignKey(fk => fk.Id).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.HasForeignKey(fk => fk.MeetingId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Meeting>()
.HasMany(n => n.Notifications)
.WithOne(m => m.Meeting)
.HasForeignKey(fk => fk.MeetingId).OnDelete(DeleteBehavior.NoAction);
... but they don't seem to do anything. It's just the same error message every time
Update
I finally found a way!
modelBuilder.Entity<MeetingNotification>()
.HasOne(m => m.Meeting)
.WithMany(n => n.Notifications)
.OnDelete(DeleteBehavior.Restrict);
... and the error went away! But I don't fully understand why, so any good explanation would be appreciated!
I have a problem with many to many relationship in EF core. I have the following models:
public class Institution
{
[Key]
public int Id { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
public class InstitutionDepartment
{
[Column("Institution_Id")]
public int InstitutionId { get; set; }
[Column("Department_Id")]
public int DepartmentId { get; set; }
public Institution Institution { get; set; }
public Departments Department { get; set; }
}
public class Departments
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Published { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
I followed the many tutorials explaining how to correctly map these classes:
modelBuilder.Entity<InstitutionDepartment>()
.HasKey(x => new { x.DepartmentId, x.InstitutionId});
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Institution)
.WithMany(p => p.InstitutionDepartments)
.HasForeignKey(pt => pt.InstitutionId);
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Department)
.WithMany(t => t.InstitutionDepartments)
.HasForeignKey(pt => pt.DepartmentId);
I wrote my query:
var institutions = _context.Institutions
.Include(i => i.InstitutionDepartments)
.ThenInclude(id => id.Department);
But no matter what I do, I get the following error:
Invalid column name 'InstitutionId'.
Can anyone tell me what I'm doing wrong here ? D:
Note I don't get the error if I don't write the .ThenInclude(id => id.Department); part.
But that make the data incomplete
The issue came from a line of code that I didn't deem relevant at the time (I'll know better next time)
This was in the Institution model without the [NotMapped] annotation:
[NotMapped]
public IEnumerable<Departments> Departments
=> InstitutionDepartments?.Select(o => o.Department);
This was causing EF to look for a missing One to Many relationship between Institution and Department
I have entity Program that has two foreign keys to my subject table (MainContactSubjectId, SecondaryContactSubjectId). Both main and secondary are nullable longs. For some reason, when I try to insert entity Program it errors (Internal Server Error) and will not let me insert unless Main and Secondary are present. Below is my entity Program and some of my dbContext. Can anyone see what I am doing wrong?
[Table("Program")]
public class Program : Entity<long>
{
[Required]
public int TenantId { get; set; }
[Required]
public long ProgramTypeId { get; set; }
[Required]
[MaxLength(4000)]
public string ProgramName { get; set; }
public long? MainContactSubjectId { get; set; }
public long? SecondaryContactSubjectId { get; set; }
public virtual ICollection<AppTables.Case_ProgramRequirements.Case_ProgramRequirement> Case_ProgramRequirement { get; set; }
public virtual AppTables.ProgramTypes.ProgramType ProgramType { get; set; }
public virtual AppTables.Subjects.Subject MainSubject { get; set; }
public virtual AppTables.Subjects.Subject SecondarySubject { get; set; }
}
I'm guessing the problem is here but I'm not sure what it is. My best
guess is the .HasRequired but I'm not sure how to rewrite it.
Without this code, the foreign keys are not getting created correctly
and circular reference issues. The WillCascadeOnDelete(false) stops the
circular reference issue.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
Edit 1:
I am fairly confident my problem is in the data access layer but if not, I am making an ajax call directly to my Application Service layer "Create". As I step through it everything looks perfect. It hits the return and then I get Internal Server Error. No other details. I tried wrapping in try/catch and catch is never hit.
I am using Asp.net Boilerplate framework. I have posted in their private forum too with no luck, yet. I just assumed it was a data access layer issue which is why I posted here.
public async Task<> Create(ProgramInput model)
{
Program domainModel = new Program();
domainModel.TenantId = (int)AbpSession.TenantId;
domainModel.ProgramName = model.ProgramName;
domainModel.ProgramTypeId = model.ProgramTypeId;
domainModel.MainContactSubjectId = model.MainContactId;
domainModel.SecondaryContactSubjectId = model.SecondaryContactId;
domainModel.CreatedBy = (long)AbpSession.UserId.Value;
domainModel.CreatedDate = Clock.Now;
domainModel.IsDeleted = false;
await _programRepository.InsertAsync(domainModel);
return;
}
Edit 2:
Here is my Subject Table. It has 50 some columns and 10 foreign keys but I will shorten with just the pertinent data.
public class Subject : Entity<long>
{
public Subject()
{
this.ProgramsMain = new HashSet<AppTables.Programs.Program>();
this.ProgramsSecondary = new HashSet<AppTables.Programs.Program>();
}
[Required]
public int TenantId { get; set; }
[Required]
public long SubjectTypeId { get; set; }
[MaxLength(1000)]
public string FirstName { get; set; }
[MaxLength(1000)]
public string MiddleName { get; set; }
[MaxLength(1000)]
public string LastName { get; set; }
[MaxLength(100)]
public string Suffix { get; set; }
[MaxLength(3000)]
public string FullName { get; set; }
//A TON MORE COLUMNS ....
public virtual AppTables.SubjectTypes.SubjectType SubjectType { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsMain { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsSecondary { get; set; }
ANSWER
I figured out my answer. I switched .HasRequired to .HasOptional and everything now works. Sorry to have wasted everyone's time. Thanks!
From the OP's answer in his question edit and comment:
Change .HasRequired to .HasOptional as per the code below.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
I have two entity model, OptionWidget and OptionWidgetValue, OptionWidget have many OptionWidgetValue like a option list, and sometimes one of those values will be a default one. I know that I can add one more field in OptionWidgetValue like Default to implement this relationship. But when I try to make another way, that is define the default in OptionWidget as the code below, I encounter some errors:
The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
The following is my model definition:
public class OptionWidget : ModelBase
{
[MaxLength(100)]
public string Name { get; set; }
public int? DefaultValueId { get; set; }
[ForeignKey("DefaultValueId")]
public virtual OptionWidgetValue DefaultValue { get; set; }
[Required]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public virtual ICollection<OptionWidgetValue> OptionWidgetValues { get; set; }
}
public class OptionWidgetValue : ModelBase
{
public string Value { get; set; }
public decimal UnitPrice { get; set; }
public virtual ICollection<ValueDependency> Dependencies { get; set; }
[Required]
public int OptionWidgetId { get; set; }
[ForeignKey("OptionWidgetId")]
public virtual OptionWidget OptionWidget { get; set; }
}
For the 1-many relationship, I define them with fluent API like this and it works fine. But how should I define the 1-0..1 relationship for the default value. Please help me if you know, thank you very much.
builder.Entity<OptionWidget>()
.HasMany(e => e.OptionWidgetValues)
.WithRequired()
.HasForeignKey(e => e.OptionWidgetId)
.WillCascadeOnDelete(false);
You could use following Fluent API configuration:
modelBuilder.Entity<OptionWidget>()
.HasOptional(e => e.DefaultValue)
.WithOptionalPrincipal();
This code makes to side of relation optional but if you want make OptionWidget required for OptionWidgetValue use this:
modelBuilder.Entity<OptionWidget>()
.HasOptional(e => e.DefaultValue)
.WithRequired();
Hi I have following entities:
public class EntityOne
{
public int EntityOneID { get; set; }
public string Smth { get; set; }
public string User1Id { get; set; }
public string User2Id { get; set; }
public virtual ApplicationUser User1 { get; set; }
public virtual ApplicationUser User2 { get; set; }
}
public class ApplicationUser : IdentityUser
{
public virtual ICollection<EntityOne> EntityOnes { get; set; }
}
I configured Relationships
modelBuilder.Entity<EntityOne>()
.HasRequired<ApplicationUser>(s => s.User1)
.WithMany(s => s.EntityOnes)
.HasForeignKey(s => s.User1Id).WillCascadeOnDelete(false);
modelBuilder.Entity<EntityOne>()
.HasRequired<ApplicationUser>(s => s.User2)
.WithMany(s => s.EntityOnes)
.HasForeignKey(s => s.User2Id).WillCascadeOnDelete(false);
When I delete one of ApplcationUser from my entity everything is OK, but if I have 2 Application users, it gives me following error:
Schema specified is not valid. Errors: The relationship 'SomeProject.Models.EntityOne_User1' was not loaded because the type 'SomeProject.Models.ApplicationUser' is not available.
I want to 2 different users having relation with "EntityOne"
EDIT: Propably answer:
public class ApplicationUser : IdentityUser
{
public virtual ICollection<EntityOne> EntityOnes1 { get; set; }
public virtual ICollection<EntityOne> EntityOnes2 { get; set; }
}
When I created 2 Collections in ApplicationUser everything works.
You can't have two different relationships associated with same property.
Navigation properties provide a way to navigate an association between two entity types. Every object can have a navigation property for every relationship in which it participates.
https://msdn.microsoft.com/en-us/data/jj713564.aspx