Entity Framework Core Self reference optional property - c#

I am using Microsoft.EntityFrameworkCore.Sqlite v1.0.1.
This is my entity:
public class Category
{
public int CategoryId { get;set;}
public Category ParentCategory { get; set; }
public int? ParentCategoryId { get; set; }
public string Name { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
}
So i need to configure ParentCategory as an optional property. How can i do this in EF core?
This is my fluent mapping until now:
modelBuilder.Entity<Category>(e =>
{
e.HasKey(c => c.CategoryId);
e.Property(c => c.DateCreated).ValueGeneratedOnAdd();
e.Property(c => c.DateModified).ValueGeneratedOnAddOrUpdate();
});

Your ParentCategoryId is already optional based on the fact that it is nullable. If it wasn't nullable, it would be required. You don't need to configure it further.
Note, you don't need to configure CategoryId as the Key for Category. It will be configured as the key because it already follows the naming convention for an entity key.

Related

C# EF Core: How to map One-to-Zero with multiple properties (with Attributes only) [With Workaround]?

Community,
I tried my first Entity Framework Core project. Now I am unable to map the properties in my models, these are NOT One-to-One relations, I would call it "One-to-Zero":
public class ContactDetails
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("CreatedById")]
public User CreatedBy { get; set; }
[ForeignKey("UpdatedById")]
public User UpdatedBy { get; set; }
[ForeignKey("DeletedById")]
public User DeletedBy { get; set; }
// foreign keys
public int? CreatedById { get; set; }
public int? UpdatedById { get; set; }
public int? DeletedById { get; set; }
}
public class User
{
public int Id { get; set; }
public string MailAdress { get; set; }
public string Password { get; set; }
[ForeignKey("ContactDetailsByUserId")]
public ContactDetails ContactDetailsByUser { get; set; }
[ForeignKey("ContactDetailsByAdminId")]
public ContactDetails ContactDetailsByAdmin { get; set; }
// foreign keys
public int? ContactDetailsByUserId { get; set; }
public int? ContactDetailsByAdminId { get; set; }
}
So ContactDetails can have three different Users.
Usercan have two different ContactDetails.
If I try to create a migration for this, I get this error:
Unable to determine the relationship represented by navigation property 'ContactDetails.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I really want to do this with Attributes only, if this is possible, anyway with the Fluent-API this works:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<ContactDetails>().HasOne(c => c.CreatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.CreatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.CreatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.UpdatedBy).WithOne().HasForeignKey<ContactDetails>(c => c.UpdatedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.UpdatedById).IsUnique(false);
builder.Entity<ContactDetails>().HasOne(c => c.DeletedBy).WithOne().HasForeignKey<ContactDetails>(c => c.DeletedById).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<ContactDetails>().HasIndex(c => c.DeletedById).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByUser).WithOne().HasForeignKey<User>(u => u.ContactDetailsByUserId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByUserId).IsUnique(false);
builder.Entity<User>().HasOne(u => u.ContactDetailsByAdmin).WithOne().HasForeignKey<User>(u => u.ContactDetailsByAdminId).OnDelete(DeleteBehavior.ClientSetNull);
builder.Entity<User>().HasIndex(u => u.ContactDetailsByAdminId).IsUnique(false);
}
Maybe the Fluent thing will help someone, but is it possible to do this with Attributes only?
Thanks for your time.
Best Regards
Stewie

Wrong Query Generated with EF Core 3.0

I am trying to use a simple where clause to retrieve data from a SQL Server. However the generated query is incorrect.This query worked perfectly with EF Core 2.2 but with EF Core 3 it throws an exception.
public async Task<List<CharacterReplacements>> GetReplacementsAsync(int? replacementSetId)
{
var replacementQuery = _context.CharacterReplacements.AsQueryable();
if (replacementSetId.HasValue)
{
replacementQuery = replacementQuery.Where(r => r.CharacterReplacementSetID == replacementSetId.Value); // .AsQueryable();
}
var replacementList = await replacementQuery.ToListAsync();
return replacementList;
}
[Serializable]
[Table("CharacterReplacementSets", Schema = "SYSTEM")]
public class CharacterReplacementSets
{
[NavigationPropertyKey]
[Key]
public int CharacterReplacementSetID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<CharacterReplacements> CharacterReplacements { get; set; }
public ICollection<FormatField> FormatFields { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string UpdatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
public string DefaultEncoding { get; set; } // Default character set
public string DefaultCharacter { get; set; }
public CharacterReplacementSets()
{
CharacterReplacements = new List<CharacterReplacements>();
}
}
[Serializable]
[Table("CharacterReplacements", Schema = "SYSTEM")]
public class CharacterReplacements
{
[NavigationPropertyKey]
[Key]
public int CharacterReplacementID { get; set; }
public char OriginalCharacter { get; set; }
public string ReplacementCharacter { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string UpdatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
[ForeignKey("CharacterReplacementSets")]
public int CharacterReplacementSetID { get; set; }
}
Expected result- Retrieve all CharacterReplacements where the replacementSetId equals the provided replacementSetId.
Actual result-
Microsoft.Data.SqlClient.SqlException: 'Invalid column name 'CharacterReplacementSetsCharacterReplacementSetID'.
Could someone kindly help me out with this?
The problem is not the specific query, but the model mapping.
First, the ForeignKey attribute here
[ForeignKey("CharacterReplacementSets")]
public int CharacterReplacementSetID { get; set; }
has no effect. When applied to navigation property, it's supposed to specify the FK property name. And when applied on FK property as here, it's supposed to specify the navigation property name. CharacterReplacements has no navigation property called CharacterReplacementSets, so the attribute is simply ignored. It would be good if EF Core generates runtime error to indicate a mapping problem, but it doesn't.
The attribute has been ignored in EF Core 1.x / 2.x as well. However it worked because the name of the property CharacterReplacementSetID matches the name of the PK of CharacterReplacementSets. This is no more true for EF Core 3.0 due to the following breaking change - The foreign key property convention no longer matches same name as the principal property.
So remove the incorrect and misleading ForeignKey attribute, and configure the FK property by either HasForeignKey fluent API (my preferred):
modelBuilder.Entity<CharacterReplacementSets>()
.HasMany(e => e.CharacterReplacements)
.WithOne()
.HasForeignKey(e => e.CharacterReplacementSetID);
or with ForegnKey attribute on navigation property (or inverse navigation property when there is no navigation property as here):
[ForeignKey("CharacterReplacementSetID")]
public ICollection<CharacterReplacements> CharacterReplacements { get; set; }
Note that you might have similar problem with FormatField and other entities using similar named FKs w/o navigation properties.
Another way to avoid this issue is to use singular entity class names like CharacterReplacementSet, CharacterReplacement etc. because the [entity name] + ID still matches EF Core conventions. And in general singular class names are better/preferable, even just for readability.

Using Entity Framework with existing database error: "Invalid column" despite data annotation

I am working with an existing database with no FK constraints and I am using .NET MVC4 template with WebApi. I am trying to create a model like this
namespace My.Models {
[Table("Risk")]
public class Risk {
[Key]
[Column("RiskId")]
public long Id { get; set; }
public string Description { get; set; }
public int Severity { get; set; }
public string Status { get; set; }
public int Occurrence { get; set; }
public int Grade { get; set; }
[Column("CreatedBy")]
public UserProfile CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
}
}
The UserProfile is the Account model from the template including UserId, Email and Password.
[Table("UserProfile")]
public class UserProfile {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string Email { get; set; }
}
When I try and run the following query -
var risks = (from h in db.Risks
//where x.SomeField == SomeValue
orderby h.CreatedDate descending
select h).Skip((page - 1) * pageSize).Take(pageSize);
I get an error: System.Data.SqlClient.SqlException: Invalid column name 'UserProfile_UserId'.
I thought my Column annotation had already flagged to EF what it should be looking for. It seems to keep appending "_UserId" to whatever I say the column name is.
CreatedBy is a navigation property. You can't apply the [Column] attribute to navigation properties. In order to configure the name of the foreign key column in the database you must use Fluent API. With your model it is not possible with data annotations alone:
modelBuilder.Entity<Risk>()
.HasOptional(r => r.CreatedBy) // or HasRequired
.WithMany()
.Map(m => m.MapKey("CreatedBy")); // this is the FK column name
Alternatively you can introduce the foreign key as property into your model and then use annotations:
[Table("Risk")]
public class Risk {
[Key]
[Column("RiskId")]
public long Id { get; set; }
//...
[Column("CreatedBy")]
public int? CreatedById { get; set; } // or int if CreatedBy is required
[ForeignKey("CreatedById")]
public UserProfile CreatedBy { get; set; }
}

EF Code First Fluent Mapping: 0-1 to Many: HasOptional(), same table

I have a "Category" Entity as follow:
public class Category
{
//<Summary>
//Fields...
//</Summary>
public Guid CategoryId { get; set; }
public string CategoryName { get; set; }
public bool IsDelete { get; set; }
// Fields for relationships
public Guid MainCategoryId { get; set; }
public Category MainCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
As seen above I want to create 0-one-to-many relationship in same table. I used Fluent API for this as follows:
HasRequired(category => category.MainCategory)
.WithMany(category => category.ChildCategories)
.HasForeignKey(category => category.MainCategoryId);
But it is a one-to-many, isn't 0-1-to-many. I use HasOptional, but it give me an error.
How can I do this with Fluent API?
thanks for reply
Make the MainCategoryId property nullable:
public Guid? MainCategoryId { get; set; }
And then you can use HasOptional method:
HasOptional(category => category.MainCategory)
.WithMany(category => category.ChildCategories)
.HasForeignKey(category => category.MainCategoryId);

Mapping child items of same class with Entity Framework Code First

I'm trying to map a fairly "standard" category model using EF Code First
public class Category
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public Category ParentCategory { get; set; }
public List<Category> ChildCategories { get; set; }
}
I've got something along the lines of:
modelBuilder.Entity<Category>()
.HasOptional(t => t.ParentCategory)
.WithMany()
.HasForeignKey(t => t.ParentCategoryID)
.WillCascadeOnDelete();
But this doesn't seem to take care of ChildCategories??
Am I missing something?
To avoid the duplicate question argument, I followed the following, however didn't quite answer my specific query:
Code First Mapping for Entity Framework Hierarchy
Entity Framework CTP5 Code-First Mapping - Foreign Key in same table
Change your Entity to
public class Category
{
public int ID { get; set; }
public int? ParentID { get; set; }
public string Name { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
}
Make ParentID nullable and to allow ChildCategories to be lazy loaded, make it virtual.

Categories

Resources