Access Entity Framework autogenerated FK column - c#

I have two classes, where Plan has a list of PlanTask, like below. However when it was initially made, there was not made a Property for the Foreign Key to go from PlanTask to Plan. However now i need the ID of Plan on PlanTask. My problem lies in because of the on to many relationship that exists between these two, Entity Framework created a Foreign Key column. However as i have no property to access that column, i can't access the id. So my question is, can i access the autogenerated column somehow, or can i edit my classes so that i don't delete the data that already exists.
Plan:
public class Plan
{
public int PlanId { get; set; }
[DisplayName("Navn")]
public string Name { get; set; }
public IList<PlanTask> Tasks { get; set; }
public bool IsActive { get; set; }
public ICollection<Department> Departments { get; set; }
public bool IsCompleted { get; set; }
public PlanStages Stage { get; set; }
public DateTime? ApprovedDate { get; set; }
[DisplayName("Ejer")]
public User Owner { get; set; }
[DisplayName("Ejer e-mail")]
public string OwnerMail { get; set; }
}
PlanTask:
public class PlanTask
{
public int PlanTaskId { get; set; }
public bool IsActive { get; set; }
[DisplayName("Beskrivelse/Årsag")]
[Required(ErrorMessage = "Indtast venligst en beskrivelse/årsag")]
public string Description { get; set; }
[DisplayName("Løsning")]
public string Solution { get; set; }
[DisplayName("Ansvarlig e-Mail")]
[Required(ErrorMessage = "Indtast venligst den ansvarliges mail")]
public string ManagerMail { get; set; }
[DisplayName("Ansvarlig navn")]
[Required(ErrorMessage = "Indtast venligst den ansvarliges navn")]
public string ManagerName { get; set; }
[DisplayName("Videresend til navn")]
public string ForwardName { get; set; }
[DisplayName("Videresend til e-Mail")]
public string ForwardMail { get; set; }
[DisplayName("Dato udført")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? DateCompleted { get; set; }
[DisplayName("Deadline for udførelse")]
public DateTime? Deadline { get; set; }
[DisplayName("Dato der er fulgt op")]
public DateTime? FollowupDate { get; set; }
[DisplayName("Påmindelses dato")]
public DateTime? ReminderDate { get; set; }
[DisplayName("Vedhæft fil")]
public string FileName { get; set; }
}
DataContext:
public class DataContext : IdentityDbContext<User>
{
public DataContext() : base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("AspNetUsers");
modelBuilder.Entity<IdentityUserRole>().ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");
modelBuilder.Entity<IdentityRole>().ToTable("AspNetRoles");
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
modelBuilder.Entity<User>()
.HasMany(x => x.Departments)
.WithMany(c => c.Users);
modelBuilder.Entity<Department>()
.HasMany(x => x.Users)
.WithMany(c => c.Departments);
base.OnModelCreating(modelBuilder);
}
public static DataContext Create()
{
return new DataContext();
}
public DbSet<Template> Templates { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<Question> Questions { get; set; }
public DbSet<SubmittedSheet> SubmittedSheets { get; set; }
public DbSet<SubmittedQuestion> SubmittedQuestions { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Role> WorkRoles { get; set; }
public DbSet<Actionplan> Plans { get; set; }
public DbSet<ActionPlanTask> PlanTasks { get; set; }
//public DbSet<User> Users { get; set; }
}

or can i edit my classes so that i don't delete the data that already exists.
Yes. Just add
public Plan Plan {get; set; }
[Column("Plan_PlanId")]
[ForeignKey("Plan")]
public int PlanId { get; set; }
and make sure it exactly matches the name of the generated column. You can use attributes for that.
Test it with a little care but when you do it right and then do an add-migration that new migration should be empty.

Related

How to properly map Entity Framework entities to DTO and vice-versa?

In the data access level, I have defined such an entity:
public class Instagram
{
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsActive { get; set; }
public bool IsSelected { get; set; }
public long UserId { get; set; }
public User User { get; set; } = null!;
public Proxy? Proxy { get; set; }
public int? ProxyId { get; set; }
public List<Work>? Works { get; set; }
}
At the abstraction level, there is such a DTO:
public class InstagramDto
{
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsSelected { get; set; }
public bool IsActive { get; set; }
public UserDto? User { get; set; }
public ProxyDto? Proxy { get; set; }
}
Did I form the DTO correctly? It maps well to one side (from EF to DTO), but the problem is reverse mapping. I do it like this:
CreateMap<InstagramDto, Instagram>()
.ForMember(x => x.UserId,
expression => expression.MapFrom((dto, _) => dto.User?.Id))
.ForMember(x => x.User, expression => expression.Ignore())
.ForMember(x => x.Proxy,
expression => expression.MapFrom((dto, _) => dto.Proxy?.Id))
.ForMember(x => x.Proxy, expression => expression.Ignore());
CreateMap<Instagram, InstagramDto>();
That is, InstagramDto must have UserDTO and ProxyDto so that I can correctly map the entity from DTO to ef. At the same time, the user may have some other navigation properties that are not involved when receiving Instagram. This means that I cannot update the User, as its navigation properties are not loaded in this situation. Is this the right approach or would it be better to do so:
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
public string? StateData { get; set; }
public string? TwoFactorLoginInfo { get; set; }
public string? ChallengeLoginInfo { get; set; }
public bool IsSelected { get; set; }
public bool IsActive { get; set; }
public long UserId { get; set; }
public int ProxyId { get; set; }

Many to Many relationship - Entity Framework Core

I have two tables in my database:
Contact
ContactRoles
The join table is Contact_ContactRole.
I have defined them in code as
Contact.cs
ContactRoles.cs
Contact_ContactRole.cs
I have defined a virtual property as follows:
public class Contact
{
public Contact()
{
}
public int ID { get; set; }
public int ClientID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public int IsDefault { get; set; }
public bool IsActive { get; set; }
public bool IsInvoiceEmail { get; set; }
public DateTime CreationDate { get; set; }
public bool IsSubConsultant { get; set; }
public string Notes { get; set; }
//[NotMapped]
public virtual ICollection<ContactRoles> ContactRoles { get; set; }
}
public class ContactRoles
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact_ContactRole
{
public int ContactID { get; set; }
public int ContactRoleID { get; set; }
//public Contact Contact { get; set; }
//public ContactRoles ContactRole { get; set; }
}
AuthContext.cs
protected override void OnModelCreating(ModelBuilder builder)
{
builder
.Entity<Contact>()
.ToTable("Contact");
builder
.Entity<Contact_ContactRole>()
.ToTable("Contact_ContactRole");
builder
.Entity<ContactRoles>()
.ToTable("ContactRoles");
builder.Entity<Contact>()
.HasMany(p => p.ContactRoles)
.WithMany(p => p.Contacts)
.UsingEntity(j => j.ToTable("Contact_ContactRole"));
builder
.Entity<Contact_ContactRole>()
.HasKey(ccr => new { ccr.ContactID, ccr.ContactRoleID });
}
I am getting an error:
Cannot use table 'Contact_ContactRole' for entity type 'Contact_ContactRole'
since it is being used for entity type
'ContactContactRoles (Dictionary<string, object>)' and potentially other
entity types, but there is no linking relationship. Add a foreign key
to 'Contact_ContactRole' on the primary key properties and pointing to the
primary key on another entity typed mapped to 'Contact_ContactRole'.'
If I comment the code:
builder.Entity<Contact>()
.HasMany(p => p.ContactRoles)
.WithMany(p => p.Contacts)
.UsingEntity(j => j.ToTable("Contact_ContactRole"));
I get the following error:
Invalid object name 'ContactContactRoles'.
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
Try this
public class Contact
{
public Contact()
{
}
public int ContactId { get; set; } //PK
public int ClientID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
public int IsDefault { get; set; }
public bool IsActive { get; set; }
public bool IsInvoiceEmail { get; set; }
public DateTime CreationDate { get; set; }
public bool IsSubConsultant { get; set; }
public string Notes { get; set; }
[ForeignKey("ContactId")]
public virtual ICollection<Contact_ContactRole> ContactContactRoles { get; set; }
}
public class ContactRoles
{
public int ContactRoleId { get; set; } //PK
public string Name { get; set; }
[ForeignKey("ContactRoleId")]
public virtual ICollection<Contact_ContactRole> ContactContactRoles { get; set; }
}
public class Contact_ContactRole
{
public int ContactId { get; set; }
public Contact Contact { get; set; }
public int ContactRoleId { get; set; }
public ContactRoles ContactRole { get; set; }
}
also I believe that Contact_ContactRole needs a Primary Key. Currently the data annotations in EF Core does not have the option of creating Composite Primary Key.
Hence, we may have to fall back to Fluent API to create the Composite Key.
modelBuilder.Entity<Contact_ContactRole>()
.HasKey(e => new { e.ContactId, e.ContactRoleId });

Entity Framework Core not loading related data from reference table

MY Model M:M relationship Reference to
https://www.entityframeworktutorial.net/efcore/configure-many-to-many-relationship-in-ef-core.aspx
Models
public class Post
{
[Key]
public int Id { get; set; }
[Display(Name = "Created By:")]
public AppUser AuthorId { get; set; }
[Required]
public string Title { get; set; }
public string metaTitle { get; set; }
[Required]
public string Body { get; set; }
public bool Published { get; set; }
public bool ISFeatured { get; set; }
public DateTime CretedDate { get; set; } = DateTime.Now;
public DateTime ModifiyDate { get; set; } = DateTime.Now;
public IList<Comment> Comments { get; set; }
public IList<PostTag> PostTag { get; set; }
public IList<PostCategory> PostCategory { get; set; }
public IList<Images> Images { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public bool Published { get; set; } = true;
public DateTime CretedDate { get; set; } = DateTime.Now;
public DateTime ModifiyDate { get; set; } = DateTime.Now;
public IList<PostTag> PostTag { get; set; }
public IList<Images> Images { get; set; }
}
public class PostTag
{
public int TagId { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public Tag Tag { get; set; }
public AppUser AppUser { get; set; }
}
DB context
modelBuilder.Entity<Post>()
.HasMany(c => c.Comments)
.WithOne(e => e.Post);
modelBuilder.Entity<PostCategory>().HasKey(p => new
{
p.PostId,p.CategoryId
});
modelBuilder.Entity<PostCategory>()
.HasOne(p => p.post).
WithMany(p => p.PostCategory).
HasForeignKey(p => p.PostId);
modelBuilder.Entity<PostCategory>().
HasOne(p => p.Category).
WithMany(p => p.PostCategory).
HasForeignKey(p => p.CategoryId);
On the controller, side fetching all posts, it is bringing all the posts but not getting any data from the related tables. Example Tags, Categories
Controller
public async Task<IActionResult> Index()
{
return View(await _context.Post.ToListAsync());
}
Update Action
Tags reference is empty
Use ThenInclude to continue including further levels of related data.
var posts = _context.Posts.Include(p => p.PostTag).ThenInclude(pt => pt.Tag).ToList();
try _context.Post.Include(x => x.PostCategory) and so on.
Reference: https://learn.microsoft.com/en-us/ef/core/querying/related-data

EF 6: Include not building navigation properties

I cant seem to figure out why my navigation property is not getting built by my include statement.
Here is my method:
public async Task<IHttpActionResult> GetCompanies(string id)
{
DbContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
var company = await DbContext.Companies.Where(x => x.Id.ToString() == id).Include(x => x.StartelAccounts).FirstOrDefaultAsync();
if (company != null)
{
return Ok(this.TheModelFactory.Create(company));
}
return NotFound();
}
When I test the SQL from the debug log I get all the fields and values for both objects.
Here are the models:
public class CompanyGroup
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime FirstBillingDate { get; set; }
[Required]
public int TermLength { get; set; }
public virtual ICollection<ApplicationUser> Members { get; set; }
public virtual ICollection<AccountStartel> StartelAccounts { get; set; }
public CompanyGroup()
{
Members = new HashSet<ApplicationUser>();
StartelAccounts = new HashSet<AccountStartel>();
}
}
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public virtual CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
Company = new CompanyGroup();
CompanyId = Guid.Empty;
UsageReports = new List<UsageReport>();
}
}
EF Fluent API
modelBuilder.Entity<AccountStartel>()
.HasRequired<CompanyGroup>(x => x.Company)
.WithMany(x => x.StartelAccounts)
.HasForeignKey(x => x.CompanyId);
modelBuilder.Entity<AccountStartel>()
.Property(p => p.DbId)
.IsRequired()
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(
new System.ComponentModel.DataAnnotations.Schema.IndexAttribute("IX_StartelDbId", 1) { IsUnique = true }));
Can anyone see what im missing here?
Could it have to do with setting Company and/or CompanyId in the
AccountStartel constructor? Does it work if you remove those lines? –
Peter
Initializing the navigation properties to a default value caused EF to not load them correctly.
Here is the updated model which does work now
public class AccountStartel
{
[Key]
public Guid Id { get; set; }
[Required]
public string ClientID { get; set; }
[Required]
public int DbId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string TimeZone { get; set; }
[Required]
public string AccountNum { get; set; }
public Guid CompanyId { get; set; }
public CompanyGroup Company { get; set; }
public virtual ICollection<UsageReport> UsageReports { get; set; }
public AccountStartel()
{
UsageReports = new List<UsageReport>();
}
}

How modelbuilder works

i have a question about relations between entities created in Code-First:
I have Models:
public class ProjectGroup
{
[Key]
public int ProjectGroupID { get; set; }
public string ProjectGroupName { get; set; }
//FK
public virtual ICollection<File> Files { get; set; }
public virtual ICollection<List> Lists { get; set; }
}
public class File
{
[Key]
public int FileID { get; set; }
public int ProjectGroupID { get; set; }
[Required]
[Display(Name="Ścieżka pliku")]
public string FilePath { get; set; }
[Required]
[Display(Name="Data Zapisu")]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd hh:mm:ss}", ApplyFormatInEditMode = true)]
public DateTime FileSaveDate { get; set; }//no NULL=>error
[Display(Name="Suma MD5")]
[StringLength(32)]
public string FileMD5Hash { get; set; }
public string IPHost { get; set; }
public int FileTemplateID { get; set; }
[ForeignKey("ProjectGroupID")]
public virtual ProjectGroup ProjectGroup { get; set; }
[ForeignKey("FileTemplateID")]
public virtual FileTemplate FileTemplate { get; set; }
public virtual ICollection<List> Lists { get; set; }//klucz obcy dla listy
}
public class List
{
[Key]
public int ListID { get; set; }
public int UserID { get; set; }
public int ProjectGroupID { get; set; }
public int FileID { get; set; }
public bool Modified { get; set; }
public bool Verified { get; set; }
public bool Alive { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; } //referencja,przekazanie nazwy FK
[ForeignKey("ProjectGroupID")]
public virtual ProjectGroup ProjectGroup { get; set; }
[ForeignKey("FileID")]
public virtual File File { get; set; }
}
And Context:
public class AWZDContext : DbContext
{
public AWZDContext()
{
}
public DbSet<User> Users { get; set; }
public DbSet<File> Files { get; set; }
public DbSet<List> Lists { get; set; }
public DbSet<RemotePC> RemotePCs { get; set; }
public DbSet<UserType> UserTypes { get; set; }
public DbSet<ProjectGroup> ProjectGroups { get; set; }
public DbSet<FileTemplate> FileTemplates { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<File>()
.HasRequired(f=>f.ProjectGroup)
.WithMany(t=>t.Files)
.WillCascadeOnDelete(false);
modelBuilder.Entity<List>()
.HasRequired(c => c.ProjectGroup)
.WithMany(d=>d.Lists)
.WillCascadeOnDelete(false);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
when I created models, I got the following errors: problem with multi-cascade delete. I added modelbuilder in on model creating.
First I added no parameter in WithMany() and it doubled relations in database like this
This created many double relations between List and ProjectGroup (File, the same, read below).
when changed to WithMany(d=>d.Lists) relations looks ok, made only once like between File and ProjectGroup.
Does modelBuilder double the effect of [foreignKey] in model?
Can anyone explain how this works? Why did it double relation earlier, with no parameter in WithMany()

Categories