I solved this, answer below.
I am new to EF and having a lot of difficulty trying to get an optional relationship. I am looking to have a relationship where I have ApiLogItem Model with an UserId property which can be null / anonymous user or a logged in user to track all Api calls. The goal is to have Existing Users who do some create a new object to be linked to that object. I do not want to create new Users every time a new ApiLogItem is created.
I have tried a dozen variations with virtual / foreign key attributes and I am stumped. It works great for null / anonymous user but once I attach an actual user to the ApiLogItem it will not insert. I get this error:
{"Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert
duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is
(09c0d2e2-b003-4be8-a62a-08d7268af58e).\r\nThe statement has been
terminated."}
I have tried following this tutorial but alas no luck.
https://www.learnentityframeworkcore.com/conventions/one-to-many-relationship#targetText=EF%20Core%20will%20create%20a,public%20class%20Author
public class ApiLogItem
{
[Key]
public long Id { get; set; }
[Required]
public int StatusCode { get; set; }
[Required]
public string Method { get; set; }
[MaxLength(45)]
public string IPAddress { get; set; }
public Guid? UserId { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser<Guid>
{
[MaxLength(64)]
public string FirstName { get; set; }
[MaxLength(64)]
public string LastName { get; set; }
public List<ApiLogItem> ApiLogItems { get; set; }
}
Error happens when I want to create a new ApiLogItem:
using (ApplicationDbContext _dbContext = new ApplicationDbContext(_optionsBuilder.Options))
{
_dbContext.ApiLogs.Add(apiLogItem);
await _dbContext.SaveChangesAsync();
}
I have reviewed several other stackoverflow issues and none seem to fix. You can find the repository here:
https://github.com/enkodellc/blazorboilerplate
You are calling applicationDbContextSeed.SeedDb(); in your Startup class each time you run, and in your SeedDb method, you are adding a user with a static id 09C0D2E2-B003-4BE8-A62A-08D7268AF58E.
The first time you run, it will create that user; the second time, it will fail because that user (with that id) already exists.
I figured it out. It needs a virtual in the parent and just the id in the child. I need to learn more about EF as it is not intuitive to me. Will post a better answer later today after testing.
public class ApplicationUser : IdentityUser<Guid>
{
[MaxLength(64)]
public string FirstName { get; set; }
[MaxLength(64)]
public string LastName { get; set; }
public ICollection<ApiLogItem> ApiLogItems { get; set; }
}
public class ApiLogItem
{
[Key]
public long Id { get; set; }
[Required]
public int StatusCode { get; set; }
[Required]
public string Method { get; set; }
[MaxLength(45)]
public string IPAddress { get; set; }
public Guid? UserId { get; set; }
}
Related
I am having issues trying to map two fields that are foreign keys into the same table. The use case is for a modifier and creator. My class already has the Ids, and then I wanted to add the full User object as virtual.
I am using a base class so that each of my tables have the same audit fields:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
[ForeignKey("CreatedById")]
public virtual User CreatedByUser { get; set; }
[ForeignKey("ModifiedById")]
public virtual User ModifiedByUser { get; set; }
}
The child class is very simple:
public class CircleUserSubscription : Entity
{
[Required]
public long Id { get; set; }
public long SponsorUserId { get; set; }
[ForeignKey("SponsorUserId")]
public virtual User User { get; set; }
public long TestId { get; set; }
[ForeignKey("TestId")]
public virtual User Test { get; set; }
}
This is a standard junction table.
When I try to generate the migration, I am getting errors that I don't understand fully.
Unable to determine the relationship represented by navigation property 'CircleUserSubscription.User' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I tried what this answer had, but the code is basically the same: https://entityframeworkcore.com/knowledge-base/54418186/ef-core-2-2---two-foreign-keys-to-same-table
An inverse property doesn't make sense since every table will have a reference to the user table.
For reference, here is the User entity:
public class User : Entity
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am hoping you all can help me out, TIA :)
EDIT: One thing to note, all of this worked fine when the entity class was as follows:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
}
It was only after I added the entity that things went awry.
So, I was trying to get something with EF core. I'm doing code-first as it's best for me (I prefer it also cause no database has to be provided for users). Anyways, I'm going to have a big database with loads of relations, so I have to use foreign keys.
However, the foreign key collection is always null, even if in the database it's linked. I got these 2 models:
User:
[Table("users")]
public class User
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("name")]
[StringLength(15, MinimumLength =2)]
[Required]
public string Name { get; set; }
[Column("password")]
[StringLength(100)]
[Required]
public string Password { get; set; }
[Column("email")]
[StringLength(30, MinimumLength =6)]
[Required]
public string Email { get; set; }
public List<UserActivityPoints> Activitypoints { get; set; }
}
UserActivityPoints:
[Table("activitypoints")]
public class UserActivityPoints
{
[Key]
[Column("type")]
public int Type { get; set; }
[Column("amount")]
public int Amount { get; set; }
[Key]
[Column("user_id")]
public User User { get; set; }
}
So, one thing that already was weird is, in the database, the user_id column becomes UserId, however it is correctly linked to the users, as shown below:
A picture of PHPMyAdmin showing it's linked correctly
In my database context, I have the following code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<UserActivityPoints>().ToTable("activitypoints");
}
I run the following code:
var i = ProductionDbContext.GetInstance().Users.Find(1).Activitypoints;
I got a record shown in the following picture:
The record is added in the database
However, when I debug, i is always null, even though user 1 has a record in the activity points table. Am I doing something wrong or am I forgetting something?
In UserActivityPoints you have [Key] above both Type and UserId - EFCore thinks you are trying to make the two combined into your primary key. Your UserActivityPoints should look like:
[Table("activitypoints")]
public class UserActivityPoints
{
[Key]
[Column("type")]
public int Type { get; set; }
[Column("amount")]
public int Amount { get; set; }
[Key]
[Column("user_id")]
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
}
I've removed the key, given you a column just for ID, and an object for User. Now it should work properly. Doc: https://learn.microsoft.com/en-us/ef/core/modeling/keys
I have the following structure in database:
For the above I have the following in my C# code for EF 6.0 CodeFirst.
[Table("Person")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
[Column("FullName")]
[MaxLength(200)]
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; } = new HashSet<Task>();
}
[Table("Task")]
public class Task
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
[Column("FullName")]
[MaxLength(200)]
public string Title { get; set; }
public virtual ICollection<User> Users { get; set; } = new HashSet<User>();
}
The problem is that the collection is not getting populated at all and always returned as empty. for instance if I try:
userRepository.All.Include(x=>x.Tasks).Where(x=> x.FullName == "John").Select(x=> x.Tasks.Title).ToList();
InnerException = {"Invalid object name 'dbo.PersonTask'."}
What is the problem with my code?
You don’t have the PersonTask table in database, so no entity is found in database. You have to create the mapping table.
I'm new to c# and I have a basic problem with saving/updating data.
With two classes :
public class User
{
public int UserId { get; set; }
public string Email { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public DateTime RegisteredDate { get; set; }
public class Task
{
public int TaskId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Comment { get; set; }
public User DeclaredBy { get; set; }
}
I'm trying to save a task object (new or updated) with DeclaredBy field set
dbContext.Tasks.AddOrUpdate(task);
dbContext.SaveChanges();
User set in DeclaredBy field has an ID, but after executing SaveChanges() a new record of User appears in DB.
This is how EF works, if you add entity with other related entity then both will be stored in DB. If you want to store only one then you need to set other navigation properties to null and only set foreign key id, which currently you don't have, you should add property:
public int DeclatedById { get; set; }
Btw. this is very common problem and a lot of projects has duplicate values in db.
Maybe I should add that this will happen only if entity wasn't traced by EF context and was attached to it.
Ok let me start with my model:
Contact Method Types:
public class ContactMethodType
{
[Key]
[HiddenInput(DisplayValue = false)]
public Guid ContactMethodTypeGUID { get; set; }
[Required(ErrorMessage = "Please enter a Contact Method Type Name.")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a brief description.")]
public string Description { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<ContactMethod> ContactMethods { get; set; }
Contact Methods:
public class ContactMethod
{
[Key]
[HiddenInput(DisplayValue = false)]
public Guid ContactMethodGUID { get; set; }
public virtual ContactMethodType Type { get; set; }
public string CountryCode { get; set; }
[Required]
public string Identifier { get; set; }
public bool IsPreferred { get; set; }
}
Recipient:
public class Recipient
{
[Key]
public Guid RecipientGUID { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's First Name.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's Last Name.")]
public string LastName { get; set; }
public string Company { get; set; }
public UserGroup Owner { get; set; }
public List<ContactMethod> ContactMethods { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public User LastModifiedBy { get; set; }
public DateTime LastModifiedOn { get; set; }
public bool IsActive { get; set; }
}
I have two Contact Method Types already defined:
Email and SMS
Now I am creating a new Recipient, so I add all of the required data to my Recipient Object, and then I call:
context.Recipients.Add(myRecipient);
context.SaveChanges();
What I get is an error that I am tying to add a new ContactMethodType when one already exists. But this is supposed to be a one to many relationship, and I do not want to add a new ContactMethodType, just categorize a new Contact Method(s) for my recipient.
I am not sure when this is happening. Maybe my model is incorrect? Based on what is chosen as the type, I pull that Type object, and set it to the ContactMethod.Type variable. But like I said, instead of just linking it to an existing ContactMethodType, it is trying to re-create it, and since the GUID already exists, I get the error that the record cannot be created because the key (GUID) already exits.
Any ideas?
After discussing this offline with Marek, it boiled down to DbSet<TEntity>.Add(entity) assuming that all entities in the graph being added are new.
From The API docs for Add...
Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.
Because this model uses client generated keys, meaning that all entities have a key value assigned before they are given to the context, you can't use any of the "smarter" methods (such as DbSet<TEntity>.Attach(entity)) that would inspect key values to work out if each entity is new or existing.
After adding the new recipient, you can use call DbSet<TEntity>.Attach(entity) on each existing entity (i.e. the contact method type). Alternatively, DbContext.Entry(entity).State = EntityState.Unchanged will also let EF know that an entity is already in the database.
You could also look at DbContext.ChangeTracker.TrackGraph(...), see the API docs for more info.