Navigation property that depends on existing property - c#

I have a class Log that has a property Entity. This entity will be a reference to some other object within the App (either Customer, Supplier, Invoice or Credit) that share a base class.
public class Log
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public LogCode Code { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public string SourceID { get; set; }
public DateTime DateCreated { get; internal set; }
public bool Acknowledged { get; set; }
public EntityType EntityType { get; set; }
public BaseModel Entity { get; set; }
}
The EntityType property contains an enum that I can use to determine the type of the entity.
public enum EntityType
{
Customer,
Supplier,
Invoice,
Credit,
}
The table in the database stores the ID of this entity but as each entity type is stored in a different table I'm having difficulties gathering this entity.
I've tried modifying the setter of EntityType to gather the correct entity but the Log doesn't have any reference to the DbContext.
Without EF I would switch on the entity type and load the correct entity using different service objects but is there a way that I can set up entity framework to use the EntityType to gather the correct Entity?

I think you should implement Table per Concrete Type (TPC) approach. The only one restriction: you have to use Guid type of Id instead of int, or leave int, but at this case you should assign values to it manually.
public abstract class BaseModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get;set;}
public string SomeCommonProperty {get;set;}
}
public class Log
{
//other properties...
//EntityType is not needed, but you can leave it
public EntityType EntityType { get; set; }
public virtual BaseModel Entity { get; set; }
}
public class MyContext : DbContext
{
public DbSet<BaseModel> Entities { get; set; }
public DbSet<Log> Logs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Customers");
});
modelBuilder.Entity<Supplier>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Suppliers");
});
}
}
Usage:
var suppliers = ctx.Entities.OfType<Supplier>()
.Where(x => x.SupplierProperty == "1").ToList();
var supplier = (log.Entity as Supplier);
var customer = (log2.Entity as Customer);

Related

EF Core 7 - how to set up multiple nullable GUID foreign keys on child object without triggering foreign key conflict

I have 3 entities: Person, User, and Location.
A Person can have multiple Locations
A User can have multiple Locations
My entities are set up as such:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class PersonEntityTypeConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.Person)
.HasForeignKey(b => b.PersonId)
.IsRequired(false);
}
}
public class User
{
public Guid Id { get; set; }
public Guid? Username { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.User)
.HasForeignKey(b => b.UserId)
.IsRequired(false);
}
}
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
[ForeignKey(nameof(Person))]
public Guid? PersonId { get; set; }
public virtual Person? Person { get; set; }
[ForeignKey(nameof(User))]
public Guid? UserId { get; set; }
public virtual User? User { get; set; }
}
Problem: I tried to insert a User into my SQL Server DB. This user has one Location object within its IList<Location>? Locations collection. I am getting the following error: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Locations_Persons_PersonId".
Here is where it is going wrong:
Since Person.Id is a Guid? object, it automatically gets set to the equivalent of Guid.Empty before it is submitted to the DB. This causes the FK conflict, since the DB sees that there is no Person object in the DB with an Id set to the equivalent of Guid.Empty.
What I've tried: I saw that in previous version of EF Core, there is a .WithOptional() method that can be used in the Fluent API, but unfortunately this method is not recognized in EF Core 7. I tried to use the .IsRequired(false) method in the API, and it probably works from the DB standpoint, but my problem is that the GUID-based Id property is being set to Guid.Empty on the server before being passed to the DB, so .IsRequired(false) doesn't have the opportunity to do its job.
Am I missing something? Is there some other way to configure this?
Solution: I had a PersonDto that had a property public Guid Id { get; set; } instead of Guid? and it was being mapped back to the Person object with Guid.Empty loaded in it. Duh.
Just make them M2M relationships and the foreign keys will all be in bridge tables. eg
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Person> Persons { get; } = new HashSet<Person>();
public virtual ICollection<User> Users { get; } = new HashSet<User>();
}

Unable to determine the relationship represented by navigation 'entitiy' of type 'ICollection<entity>'

I am aware of other questions on the same topic, however I have tried their solutions and none have worked for my code.
I am configuring a one-to-many relationship with one Account with many JoinedClassIds.
Account.cs
public class Account {
// Actual account info
[Key]
public int _id { get; set; }
public string _name { get; set; }
public string _email { get; set; }
public ICollection<JoinedClassId> JoinedClasses { get; set; }
}
JoinedClassId.cs
[Keyless]
public class JoinedClassId {
public int classIdNumber { get; set; }
public Account Account { get; set; }
}
DBContext
public DbSet<Account> Accounts { get; set; }
public DbSet<JoinedClassId> JoinedClassIds { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Account>()
.HasMany(acc => acc.JoinedClasses)
.WithOne(jcid => jcid.Account);
modelBuilder.Entity<JoinedClassId>()
.HasOne(jc => jc.Account)
.WithMany(acc => acc.JoinedClasses);
modelBuilder.Entity<JoinedClassId>()
.HasNoKey();
}
I get the error
System.InvalidOperationException: 'Unable to determine the
relationship represented by navigation 'Account.JoinedClasses' of type
'ICollection'. Either manually configure the
relationship, or ignore this property using the '[NotMapped]'
attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.'
What can I do to fix this?
Keyless entities cannot participate in two way relationships. They can only contain reference navigation to other regular entities (entities with key). Also, regular entities cannot contain navigation properties to keyless entity types. What causing the issue in your case is, your Account entity contains the navigation property JoinedClasses, which is a collection of Keyless entity.
For details - Keyless entity types characteristics
Remove the [Keyless] attribute from JoinedClassId entity
Add a new key property or mark the classIdNumber property as key
Add a foreign-key property (optional)
public class JoinedClassId
{
[Key]
public int Id { get; set; } // added key property
public int classIdNumber { get; set; }
public int AccountId { get; set; } // added foreign-key property
public Account Account { get; set; }
}
Then you can configure as -
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>()
.HasMany(acc => acc.JoinedClasses)
.WithOne(jcid => jcid.Account)
.HasForeignKey(p=> p.AccountId); // if you have added a foreign-key property
}
Fix your classes:
public class Account {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public virtual ICollection<JoinedClass> JoinedClasses { get; set; }
}
public class JoinedClass {
[Key]
public int Id { get; set; }
public int ClassIdNumber { get; set; }
public int AccountId { get; set; }
public virtual Account Account { get; set; }
}
If you use EF Net5 , you don't need OnModelCreating fluent Api code. Delete all tables from db and migrate again

Relationship - EF Core

I'm having some trouble to get into EF Core relationship.
I didn't know how to search it properly, so I've not found what I need, but I got somewhere.
I have these two classes:
Expense:
public class Expense : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public List<ExpenseType> Types { get; set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
ExepenseType:
public class ExpenseType : Entity
{
#region properties
public string Name { get; private set; }
public string Description { get; private set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseTypeIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
During the ToListAsync in ExpenseType, the EF adds the column "expenseId" to the query, but this column does not exist.
My database has three tables, one for each class, and one for the relationship.
(Expense, ExpenseType and Expense_ExpenseType)
By looking for the solution here on StackOverflow I found that I should have a class for the third table.
Here it is:
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them.
So ExpenseType hasn't any Expense.
I'm not sure what I should do now.
Should I Map using optionsBuilder? How?
Should I ReWrite the database?
If you want to create Many-to-Many relationship, you have several options how to do it:
Create additional class how you described. In this case EF will create table and you can get access to get values only from this table.
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
You may don't create class and just describe in the context relationship. Where you will describe everything and EF will create by yourself this table. But from the app you will not see this table. You have to use this variant if you don't want to extend table with additional fields.
modelBuilder
.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
For this relationship you can read more here
But in your case you don't need to use Many-to-Many. That's why if you don't want to add propertie ExpanseTypeId or ExpenseId in your model you can describe it like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Expense>()
.HasMany<ExpenseType>(o => o.Types) //It is your list of expense types.
.WithOne() //Here you can add model for expense. To have an option go back from expense type to expense
.HasForeignKey("ForeignKey");//This key EF will create for you in DB but not in you app model
}
What do you want to use you have to decide. If you have an idea that expense has a lot of expensetypes and each expense type has a lot of expenses. You have to use Many-To-Many how I described.
I think that your main question is "My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them."
so you can do that by creating a nullable foreign key ExpenseTypeId in Expanse class and HashSet of Expanse in ExpeseType class.
Like this:
public class ExpenseType : Entity
{
public ICollection<Expanse> Expanses {get; set;} = new HashSet<Expanse>()
}
public class Expense : Entity
{
public int? ExpanseTypeId {get; set;}
public ExpanseType ExpanseType {get; set;}
}

Entity Framework Core Put Operation Not Working

I am getting
Unhandled Exception Error: the property 'Id' on entity type 'Vehicle' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principalThe property 'Id' on entity type 'Vehicle' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal
Here is my Put API:
[HttpPut("{id}")]
public IActionResult UpdateVehicle(int id, [FromBody] SaveVehicleResource vehicleResource)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var vehicle = context.Vehicles.Include(v => v.Features).SingleOrDefault(v => v.Id == id);
if (vehicle == null)
return NotFound();
mapper.Map(vehicleResource, vehicle);
vehicle.LastUpdate = DateTime.Now;
context.SaveChanges();
var result = mapper.Map<Vehicle, SaveVehicleResource>(vehicle);
return Ok(result);
}
Here is DbContext:
public class VegaDbContext : DbContext
{
public DbSet<Make> Makes { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Model> Models { get; set; }
public VegaDbContext(DbContextOptions<VegaDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<VehicleFeature>().HasKey(vf => new { vf.VehicleId, vf.FeatureId });
}
}
Vehicle class:
public class Vehicle
{
public int Id { get; set; }
public int ModelId { get; set; }
public Model Model { get; set; }
public bool IsRegistered { get; set; }
public Contact Contact { get; set; }
public ICollection<VehicleFeature> Features { get; set; }
public DateTime LastUpdate { get; set; }
public Vehicle()
{
Features = new Collection<VehicleFeature>();
}
}
VehicleFeature class:
public class VehicleFeature
{
public int VehicleId { get; set; }
public int FeatureId { get; set; }
public Vehicle Vehicle { get; set; }
public Feature Feature { get; set; }
}
Disclaimer: it really looks like you're using AutoMapper. So let's take a look at your AutoMapper configuration. Specifically, see if you can find something like .CreateMap<Vehicle, SaveVehicleResource> in there.
One of these two things is happening:
Your AutoMapper is configured to explicitly CreateMap for these classes and it includes a statement similar to .ForMember(x => x.Id, x.MapFrom(y => y.Id))
Your AutoMapper is not configured explicitly which means it is finding the property .Id because both classes define it with the same name. You must explicitly ignore that member.
Regardless which of those things has happened, you'll have to tell AutoMapper to ignore that property.
CreateMap<Vehicle, SaveVehicleResource>(...)
.ForMember(x => x.Id, y => y.Ignore());

EF6 - TPC inheritance fails when abstract base class has foreign key for other entity

Thank you very much in advance,
I have an abstract class called Person which has a foreign key for theCity table.
public abstract class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public Int32 Id { get; set; }
[Column("city")]
public int CityId { get; set; }
[Required]
[ForeignKey("CityId")]
public City City
{
get; set;
}
}
[Table("cities", Schema = "public")]
[Serializable]
public class City
{
public City()
{
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
[Column("id")]
public Int32 Id { get; set; }
[Column("name")]
public String Name { get; set; }
}
And later I have my Customer class that inherits fromPerson:
public class Customer:Person
{
[Column("cpfcnpj")]
public String CpfCnpj { get; set; }
}
And I'm mapping the TPC (Type per Concrete) inheritance as follows in my context class:
public class MyContext:DbContext
{
public DbSet<Person> People { get; set; }
public MyContext():base("MyConnection")
{
Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove();
modelBuilder.Entity<Customer>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("customers", "public");
});
base.OnModelCreating(modelBuilder);
}
}
When I try to pick up my customers I get the following error:
Person: : The referenced EntitySet 'Person' for End 'Person' could not
be found in the containing EntityContainer.
The code used to select customers is the following:
using (var context = new MyContext())
{
//get error in this line
var customers = context.People.OfType<Customer>().ToList();
//...
}
Note: To test, I passed the foreign key properties for the Customer concrete class and executed perfectly.
Can someone help me? I really need to understand this situation.
Thanks.

Categories