I'm creating a database with serveral classes as children of other classes. Writing with foreign keys works fine however when i try to get the data back it throws the error:
An unhandled exception of type 'SQLiteNetExtensions.Exceptions.IncorrectRelationshipException' occurred in SQLiteNetExtensions.dll
Additional information: MatchDetail.timeline: At least one entity in a OneToOne relationship must have Foreign Key
This is my creation code:
SQLiteConnection db = new SQLiteConnection(new SQLite.Net.Platform.Win32.SQLitePlatformWin32(), "Matches.db3");
db.CreateTable<ParticipantIdentity>();
db.CreateTable<MatchDetail>();
db.CreateTable<Timeline>();
db.InsertWithChildren(md, true);
var m = db.GetWithChildren<MatchDetail>(matchId, true);
And my class:
[Table("Matches")]
public class MatchDetail
{
[PrimaryKey]
public long matchId { get; set; }
public int mapId { get; set; }
[JsonConverter(typeof(DateTimeConverterFromLong))]
public DateTime MatchCreation { get; set; }
public long matchDuration { get; set; }
public MatchMode matchMode { get; set; }
public MatchType matchType { get; set; }
public string matchVersion { get; set; }
public string platformId { get; set; }
public Queuetype queueType { get; set; }
public Region region { get; set; }
public Season season { get; set; }
[ForeignKey(typeof(Timeline), Name = "TimelineId"), Indexed]
public int timelineId { get; set; }
[OneToOne("TimelineId", CascadeOperations = CascadeOperation.All)]
public Timeline timeline { get; set; }
[ForeignKey(typeof(ParticipantIdentity), Name = "ParticipantId"), Indexed]
public int participantIdentitiesId { get; set; }
[ManyToOne("ParticipantId", CascadeOperations = CascadeOperation.All)]
public List<ParticipantIdentity> participantIdentities { get; set; }
}
The other classes are just an id and some other basic types, I've been trying to work this out but it just doesnt want to work.
Edit:
[ForeignKey(typeof(ParticipantIdentity))]
public int participantIdentitiesId { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<ParticipantIdentity> participantIdentities { get; set; }
With error:
An unhandled exception of type 'SQLiteNetExtensions.Exceptions.IncorrectRelationshipException' occurred in SQLiteNetExtensions.dll
Additional information: MatchDetail.participantIdentities: Unable to find foreign key for OneToMany relationship
You are manually specifying that the foreign key is named 'TimelineId' but in reality the foreign key is named 'timelineId' (note capitalization of the first letter).
Change this:
[ForeignKey(typeof(Timeline), Name = "TimelineId"), Indexed]
public int timelineId { get; set; }
[OneToOne("TimelineId", CascadeOperations = CascadeOperation.All)]
public Timeline timeline { get; set; }
To this:
[ForeignKey(typeof(Timeline)]
public int timelineId { get; set; }
[OneToOne(CascadeOperations = CascadeOperation.All)]
public Timeline timeline { get; set; }
Explicitly declaring the foreign key name can lead to refactoring issues, so this is the recommended way unless otherwise is strictly required.
The other relationship is declared as a ManyToOne but it's in fact a OneToMany. To make it work you have to change the attribute type and move the foreign key to the other end.
Change the relationship from:
[ForeignKey(typeof(ParticipantIdentity), Name = "ParticipantId"), Indexed]
public int participantIdentitiesId { get; set; }
[ManyToOne("ParticipantId", CascadeOperations = CascadeOperation.All)]
public List<ParticipantIdentity> participantIdentities { get; set; }
With this:
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<ParticipantIdentity> participantIdentities { get; set; }
And add the foreign key to MatchDetail to the ParticipantIdentity class:
[ForeignKey(typeof(MatchDetail)]
public int matchDetailId { get; set; }
Take a look at the SQLite-Net Extensions documentation and sample project for more examples.
Related
I want to create one to one relation between tables. My table is
public class StudentModel
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime DateOfBirth { get; set; }
[Required, Display(Name="Department Name")]
public int DeptId { get; set; }
//navigration proprty
[ForeignKey("DeptId")]
public virtual DepartmentModels Department { get; set; }
public virtual StudentRegistrationModels StudentRegistration { get; set; }
}
and my other table is
public class StudentRegistrationModels
{
[Key]
public int StudentId { get; set; }
[Required]
public int CourseId { get; set; }
[Required]
public DateTime EnrollDate { get; set; }
public bool IsPaymentComplete { get; set; }
//navigration proprty
[ForeignKey("StudentId")]
public virtual StudentModel Student { get; set; }
[ForeignKey("CourseId")]
public virtual CourseModels Course { get; set; }
//oneToOneStudentRegistration
}
But when I make migration it throws an error:
Unable to determine the principal end of an association between the types 'StudentManagementSystem.Models.StudentModel' and 'StudentManagementSystem.Models.StudentRegistrationModels'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Why is this occurring?
I believe the issue is that you have a single StudentRegistrationModel instance in your StudentModel, where the StudentRegistrationModel looks to be more of a Many-to-Many structure. Can a single student only be registered to a single course? If that were the case it would make more sense for StudentModel to just have a CourseModel reference. Since a Student probably has multiple courses, it would probably make more sense for StudentModel to have:
public virtual ICollection<StudentRegistrationModels> StudentRegistration { get; set; } = new List<StudentRegistrationModels>();
Then ensuring that your model configuration maps out the relationship. This can be done with an attribute, as part of the DbContext OnModelCreating, or using an EntityTypeConfiguration. With Attributes:
[InverseProperty("Student")] // Tells EF this collection corresponds to the Student on the StudentRegistrationModel.
public virtual ICollection<StudentRegistrationModels> StudentRegistration { get; set; } = new List<StudentRegistrationModels>();
Maybe try to add [Key] annotation to Id field in StudentModel.
using System.ComponentModel.DataAnnotations;
public class StudentModel
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime DateOfBirth { get; set; }
[Required, Display(Name="Department Name")]
public int DeptId { get; set; }
//navigration proprty
[ForeignKey("DeptId")]
public virtual DepartmentModels Department { get; set; }
public virtual StudentRegistrationModels StudentRegistration { get; set; }
}
or if it won't work try map relationship in OnModelCreating in your data context.
So I am building an online shop using Code-First MVC
So I created this model classes for now (don't take the types of the attributes too serious for now):
Products (Produto):
public Produto()
{
ListaProdutoEncomenda = new HashSet<Produto_Encomenda>();
}
public int ProdutoID { get; set; }
[Required]
[StringLength(50)]
public string Nome { get; set; }
[Required]
public double Preco { get; set; }
[Required]
public double Peso { get; set; }
[Required]
[StringLength(255)]
public string Descricao { get; set; }
[Required]
public double IVA { get; set; }
public string Imagem { get; set; }
public DateTime UltimaAtualizacao { get; set; }
public int Stock { get; set; }
public int CategoriaID {get;set;}
public virtual ICollection<Produto_Encomenda> ListaProdutoEncomenda { get; set; }
}
Encomenda (Order):
public class Encomenda
{
public Encomenda()
{
ListaProdutoEncomenda = new HashSet<Produto_Encomenda>();
}
[Key]
public int IDEncomenda { get; set; }
[Required]
public DateTime DataSubmissao { get; set; }
[Required]
public DateTime DataEnvio { get; set; }
[Required]
public int EstadoEnvioID { get; set; }
[StringLength(50)]
public string NomeDestino { get; set; }
[Required]
public int TipoExpedicaoID { get; set; }
[Required]
public int RegiaoDestinoID { get; set; }
[StringLength(50)]
public string MoradaDestino { get; set; }
[StringLength(50)]
public string CodPostalDestino { get; set; }
[Required]
[StringLength(50)]
public string MoradaFaturacao { get; set; }
[Required]
[StringLength(50)]
public string CodPostalFaturacao { get; set; }
public virtual ICollection<Produto_Encomenda> ListaProdutoEncomenda { get; set; }
}
And the connection table between the produtos (Products) and Encomenda (Order)
public class Produto_Encomenda
{
[Key]
public int IDProduto_Encomenda { get; set; }
[Required]
public string NomeProduto { get; set; }
[Required]
public int Quantidade { get; set; }
[Required]
public float preco { get; set; }
[Required]
public float IVA { get; set; }
public virtual Encomenda Encomenda { get; set; }
public virtual Produto Produto { get; set; }
[ForeignKey("Encomenda")]
public int IDEncomendaFK { get; set; }
[ForeignKey("Produto")]
public int IDProdutoFK { get; set; }
}
So my question is..
Let's pretend that a costumer buys 2 or 3 products or more.
How can I store all this products in a single line of an order?
Cheers and thanks a lot in advance for the time spent reading.
I'm not sure what you mean by "a single line of an order". Each product is a separate line item, and your entities already model this through the many-to-many relationship.
However, in general this setup is a very bad idea. Your order should not be directly related to products. Instead, your order should simply have an order item, and you'll create those order items based on the products that were sold. The reason for this is that products are very likely to change. If a product is removed because it's no longer available, for example, that doesn't negate the fact that it was previously sold in an order. However, in order for referential integrity to be maintained, any orders sold with that product would have to also have their relationship with that product removed. By having an entirely separate entity, i.e. order item, products can come and go, while the already created orders remain unaffected.
I guess you are looking to make a viewmodel
Create a class that contains Products and Encomenda class as property -
Model -
public class MyViewModel
{
public Produto Pinst{get;set;}
public Encomenda Einst{get;set;}
}
Controller or method-
public void SomeMethod()
{
List<MyViewModel> lst = new List<MyViewModel>();
//Now suppose
foreach(var items in listThatGetCreatedWithBuyerproductInfo)
{
MyViewModel obj = new MyViewModel ();
obj.Pinst = new Produto();
obj.Einst = new Encomenda();
//Here goes your properties from item in respected class instances
obj.Pinst.Nome = items.Nome;
obj.Einst.DataSubmissao = items.DataSubmissao;
//when you are done loading add obj to list
lst.Add(obj);
}
}
Hope it Helps if it does not tell me !!
I have got several models:
Course.cs
public class Course
{
public Guid Id { get; set; }
public ICollection<ApplicationUser> Teacher { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
public DateTime CreationDate { get; set; }
public bool IsActive { get; set; }
}
Group.cs
public class Group
{
public Guid Id { get; set; }
public ApplicationUser Mentor { get; set;}
public string DisplayName { get; set; }
public string GroupName { get; set; }
public DateTime StartYear { get; set; }
public string InviteCode { get; set; }
public ICollection<ApplicationUser> Students { get; set; }
public ICollection<Course> Courses { get; set; }
}
ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
[Required]
public string Firstname { get; set; }
[Required]
public string Surname { get; set; }
public bool Gender { get; set; }
public DateTime Birthdate { get; set; }
//[Required]
public string InviteCode { get; set; }
public Guid GroupId { get; set; }
[ForeignKey("GroupId")]
public Group CurrentGroup { get; set; }
public ICollection<Group> PastGroups { get; set; }
}
Now when I try to register (using Identity) a user (not even trying to give the user a group) I receive this error:
SqlException: The INSERT statement conflicted with the FOREIGN KEY
constraint "FK_AspNetUsers_Groups_GroupId". The conflict occurred in
database "aspnet-Project_Dojo-3af15f80-8c62-40a6-9850-ee7a296d0726",
table "dbo.Groups", column 'Id'. The statement has been terminated.
In my modelBuilder I have added some logics for the relations between Group, ApplicationUser (Students) and the Foreign Key:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);\\
builder.Entity<ApplicationUser>()
.HasOne(p => p.CurrentGroup)
.WithMany(b => b.Students)
.HasForeignKey(p => p.GroupId);
}
I don't know what this is exactly doing, but I've been browsing some Stackoverflow threads to come to this code (migrations weren't working without it).
I look forward to a solution for my problem. Once again, I'm not doing ANYTHING with the groups yet when registering.
Thanks in advance!
not even trying to give the user a group
Well there's your problem, it's required.
Either provide a group, or make it optional by making the foreign key nullable (Guid? GroupId).
Because it's currently a non-nullable struct, it'll have a default value of all zeroes (Guid.Empty). This FK is not known in your database, resulting in the error you see.
I am trying to set up a SQLite database using SQLite.Net and SQLiteNetExtensions. I cant seem to get the foreign keys to create in the database. I have dropped all tables and used the model classes to recreate them again. I have checked the pragma in SQLite manager and foreign keys are turned on. In my example, a plot can have many trees. Any suggestions on what i might be missing?
public class Tree
{
[PrimaryKey, AutoIncrement]
public int TreeId { get; set; }
[NotNull]
public int TreeNumber { get; set; }
[NotNull]
public double Dbhob { get; set; }
public double? Height { get; set; }
[NotNull,Default(value: false)]
public bool? HeightTree { get; set; }
public string QualityCode { get; set; }
[ForeignKey(typeof(Plot))]
public int PlotId { get; set; }
[ManyToOne]
public Plot PlotInverse { get; set; }
}
public class Plot
{
[PrimaryKey, AutoIncrement]
public int PlotId { get; set; }
[NotNull, Unique]
public System.Guid PlotGuid { get; set; }
[NotNull, MaxLength(60)]
public string Strata { get; set; }
[NotNull, MaxLength(60)]
public string PlotNumber { get; set; }
public DateTime MeasureDate { get; set; }
[MaxLength(70)]
public String AssessorLead { get; set; }
[MaxLength(60)]
public String AssessorCrew { get; set; }
[Default(value: 0)]
public double PlotArea { get; set; }
public double BasalArea { get; set; }
[OneToMany("PlotId","PlotId")]
public List<Tree> Trees { get; set; }
}
Foreign keys attributes are not created in the sqlite database as sqlite-net doesn't have support for Foreign Keys and SQLite-Net Extensions is built on top of it. They are used in runtime to save and load related objects.
As a side note, you should change this line:
[OneToMany("PlotId","PlotId")]
public List<Tree> Trees { get; set; }
To this:
[OneToMany("PlotId","PlotInverse")]
public List<Tree> Trees { get; set; }
or remove it all together and let auto-discovery do its magic:
[OneToMany]
public List<Tree> Trees { get; set; }
First off, I know there are a lot of posts about the Unable to determine the principal end of an association between the types error but ever single one I see does not match my issue, if I missed one sorry about that.
I have built an Entity that will end up referencing it's self twice and when I put the code in for the first self reference it works fine, as soon as ad the code for the second it breaks. Doing some testing I have found that if I use either of the self references by them self everything works fine, it is only when I add the second self reference that it breaks. The code I am using for the self references is:
[ForeignKey("ManagerID")]
public User Manager { get; set; }
//Auditing Fields
public DateTime DateCreated { get; set; }
public int? UpdatedByUserID { get; set; }
public DateTime? DateUpdated { get; set; }
public DateTime LastAutoUpdate { get; set; }
[ForeignKey("UpdatedByUserID")]
public User UpdatedByUser { get; set; }
The full entity code block is:
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int ADPFileNumber { get; set; }
[Required]
public string ADUserName { get; set; }
public int AirCardCheckInLateCount { get; set; }
[Required]
public string Email { get; set; }
public DateTime? EndDate { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int ManagerID { get; set; }
public string MobilePhone { get; set; }
[Required]
public string Office { get; set; }
[Required]
public string Phone { get; set; }
public decimal PTO { get; set; }
public DateTime StartDate { get; set; }
public int VehicleCheckInLateCount { get; set; }
public int WexCardDriverID { get; set; }
[ForeignKey("ManagerID")]
public User Manager { get; set; }
//Auditing Fields
public DateTime DateCreated { get; set; }
public int? UpdatedByUserID { get; set; }
public DateTime? DateUpdated { get; set; }
public DateTime LastAutoUpdate { get; set; }
[ForeignKey("UpdatedByUserID")]
public User UpdatedByUser { get; set; }
}
What am I missing that cause the second self reference to break?
You have to indicate the principal end of both associations explicitly. You can do that with the class you had originally, without inverse collection properties:
modelBuilder.Entity<User>()
.HasOptional(u => u.Manager)
.WithMany()
.HasForeignKey(u => u.ManagerID);
modelBuilder.Entity<User>()
.HasOptional(u => u.UpdatedByUser)
.WithMany()
.HasForeignKey(u => u.UpdatedByUserID);
Note that ManagerID should be an int? as well. You can't create any User if it requires another user to preexist. That's a chicken-and-egg problem.
As mentionned in Multiple self-referencing relationships in Entity Framework, you seem to be missing the other part of the relationship.
i.e.
[InverseProperty("Manager")]
public virtual ICollection<User> ManagedUsers {get;set;}
[InverseProperty("UpdatedByUser")]
public virtual ICollection<User> UpdatedUsers {get;set;}
EDIT: based on #Gert Arnold's answer you should indeed add the [InverseProperty] attribute