Mapping relationships to collections of abstractions in Entity Framework - c#

I have two classes that each implement an interface. One of the classes contains an ICollection of the other's interfaces.
Now I want to map this to my database using EF but get an exception (below). Is this supposed to be possible somehow?
Entity definitions for my classes (products and categories):
public interface IProduct
{
string ProductId { get; set; }
string CategoryId { get; set; }
}
public interface ICategory
{
string CategoryId { get; set; }
ICollection<IProduct> Products { get; set; };
}
public class ProductImpl : IProduct
{
public string ProductId { get; set; }
public string CategoryId { get; set; }
}
public class CategoryImpl : ICategory
{
public string CategoryId { get; set; }
public ICollection<IProduct> Products { get; set; }
}
I want to map the relationship between CategoryImpl and ProductImpl so I'm using the following OnModelCreating method in my DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var a = modelBuilder.Entity<CategoryImpl>();
a.ToTable("Categories");
a.HasKey(k => k.CategoryId);
a.Property(p => p.CategoryId);
a.HasMany(p => p.Products).WithOptional().HasForeignKey(p => p.CategoryId);
var b = modelBuilder.Entity<ProductImpl>();
b.ToTable("Products");
b.HasKey(k => k.ProductId);
b.Property(p => p.ProductId);
}
The exception I get is below. Am I supposed to somehow specify that the concrete type to be used for IProduct is ProductImpl?
System.InvalidOperationException: The navigation property 'Products'
is not a declared property on type 'CategoryImpl'. Verify that it has
not been explicitly excluded from the model and that it is a valid navigation property.

It's not possible to do it with interfaces in EF. The type of a navigation property must be mapped for the property to be mapped. And for a type to be mapped it needs to be a concrete type among other things.
If you need to have different types of products and categories you could instead use a base class for them:
public class ProductBase
{
public string ProductId { get; set; }
public string CategoryId { get; set; }
}
public class CategoryBase
{
public string CategoryId { get; set; }
public virtual ICollection<ProductBase> Products { get; set; }
}
public class DerivedProduct : ProductBase
{
}
public class DerivedCategory : CategoryBase
{
}

Related

Configure a many to one relationship in a Owned Entity in Entity Framework

I have a class that is an entity in the DB and has a owned entity:
public class EntityOne
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I have the owned entity with a list with the type of another persisted entity:
public class OwnedEntity
{
public int Id { get; set; }
public List<EntityTwo> EntityTwo { get; set; }
}
And here is the EntityTwo:
public class EntityTwo
{
public int Id { get; set; }
// all other properties
}
I need to create the relationship between EntityOne and EntityTwo as a many to one, but the property navigation is in my owned entity. How can I do that?
I tried to create a property navigation of my owned entity like this:
public class EntityTwo
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
public int OwnedEntityId { get; set; }
// all other properties
}
And the map:
builder.HasOne(prop => prop.OwnedEntity)
.WithMany(prop => prop.EntityTwo)
.HasForeignKey(prop => prop.OwnedEntityId);
But I got an error because ef tries to make my owned entity as a entity table.
Then, I tried to reference the parent entity:
public class EntityTwo
{
public int Id { get; set; }
public EntityOne EntityOne { get; set; }
public int EntityOneId { get; set; }
// all other properties
}
And mapping with inner properties:
builder.HasOne(prop => prop.EntityOne.OwnedEntity)
.WithMany(prop => prop.EntityTwo)
.HasForeignKey(prop => prop.EntityOne.OwnedEntityId);
But it didn't work and i got another ef error:
Error: The expression 'prop => prop.EntityOne.OwnedEntity' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. (Parameter 'memberAccessExpression')
So, is there any way to create this relationship?
For those who want to know how I solved it, I did it all inside the map configuration of the entity one, using the OwnsMany. First, I changed the one-to-many reference of EntityTwo to the OwnedEntity:
public class EntityTwo
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
public int OwnedEntityId { get; set; }
// all other properties
}
Then, i made this map (builder of the EntityOne):
builder.OwnsOne(prop => prop.OwnedEntity, subBuilder =>
{
// all map configurations of OwnedEntity
subBuilder.OwnsMany(prop => prop.EntityTwo, ownedBuilder =>
{
ownedBuilder.HasOne(prop => prop.EntityTwo)
.WithMany(prop => prop.OwnedEntity)
.HasForeignKey(prop => prop.EntityTwoId);
// all map configurations of EntityTwo
}
}
That's it.

EF Core: Abstract class is handled as root entity

I use EF core to create my tables (code first) and I'm encountering strange behaviour with my abstract base classes for some entities. I have the class ConfigurableDiscountas an abstract base class and multiple classes inheriting from it, for example: AfterSalesCoverage. The classes look like this:
public abstract class ConfigurableDiscount : TenantEntity, TraceChangesEntity
{
public DealType DealType { get; set; }
[Required]
[Column(TypeName = "decimal(18,4)")]
public decimal Discount { get; set; }
}
public class AfterSalesCoverage : ConfigurableDiscount
{
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
}
The TenantEntity and TraceChangesEntity are another abstract class and an empty interface, respectively.
public abstract class TenantEntity : BaseDeleteEntity
{
public Guid BusinessUnitId { get; set; }
public virtual BusinessUnit BusinessUnit { get; set; }
}
public abstract class BaseDeleteEntity : BaseEntity
{
public DateTime? DeletedOn { get; set; }
}
public abstract class BaseEntity
{
[Required]
public DateTime CreatedOn { get; set; }
public Guid CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime? ChangedOn { get; set; }
public Guid? ChangedById { get; set; }
public virtual User ChangedBy { get; set; }
}
So as you can see, all of these are abstract except the AfterSalesCoverage.
If I try to build or migrate, I get the following error:
The entity type 'ConfigurableDiscount' requires a primary key to be defined.
If I do so (either defining a dummy key inside ConfigurableDiscount or shifting the Id from AfterSalesCoverage to ConfigurableDiscount) and try to migrate, I get the following error:
The filter expression 'entity => (entity.DeletedOn == null)' cannot be specified for entity type 'AfterSalesCoverage'. A filt
er may only be applied to the root entity type in a hierarchy.
Which makes me wonder why the AfterSalesCoverage isn't the root entity? The filter expression is defined/configured in the DBContext.
EDIT:
The filter in the DBContext is called inside of OnModelCreating(ModelBuilder builder) and looks like this:
private void SetSoftDeleteFilterQuery(ModelBuilder builder)
{
System.Collections.Generic.IEnumerable<Type> q = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.IsSubclassOf(typeof(BaseDeleteEntity))
select t;
foreach (Type type in q)
{
MethodInfo method = typeof(CPEDbContext).GetMethod("ApplySoftDeleteFilterQuery", BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo generic = method.MakeGenericMethod(type);
if (type.Name != "RequestWrapper" && type.Name != "TenantEntity" && type.Name != "BaseBenchmarkDiscount")
{
{
generic.Invoke(this, new[] { builder });
}
}
}
}
private void ApplySoftDeleteFilterQuery<Tdb>(ModelBuilder builder) where Tdb : BaseDeleteEntity
{
builder.Entity<Tdb>().HasQueryFilter(entity => entity.DeletedOn == null);
}
It seems like this filter is the center of interest in this case...
Question:
Why does the ConfigurableDiscount need a primary key? It's not supposed to be an actual entity. If a inherit directly from TenantEntity, I don't need an ID in the TenantEntity either... The ConfigurableDiscount entity is referenced nowhere but in the inheriting classes. So there is no DBSet or anything like that in my DBContext.

EF relation - cannot map due to list<guid> being primitive

I'd want to store in User class collection of Items
But I cannot overcome this problem:
System.InvalidOperationException: 'The property 'User.ItemsIds' could not be mapped, because it is of type 'List' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.'
I'm using ASP.NET Core 2.0 + EFCore
public class User
{
[Key]
public Guid Id { get; set; }
[ForeignKey("Items")]
public virtual List<Guid> ItemsIds{ get; set; }
(...)
}
public class Item
{
[Key]
public Guid Id { get; set; }
public string ItemName { get; set; }
public Item(string name)
{
Id = new Guid();
ItemName = name;
}
}
public class AppContext : DbContext
{
public AppContext(DbContextOptions<AppContext> options) : base(options)
{
Database.SetCommandTimeout(35000);
}
public DbSet<User> Users { get; set; }
public DbSet<Item> Items { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Item>().ToTable("Items");
}
}
Here's similar problem, but still it hasn't been "fixed"?
Entity Framework - Code First - Can't Store List<String>
You can't store dependent entity identifiers only.
Principal entity User must have collection of Item:
public class User
{
[Key]
public Guid Id { get; set; }
public virtual List<Item> Items { get; set; }
// ...
}
how about use Item object in the collection instead of itemIDs and use the Fluent API to map the relationship one to many. use Has/With pattern to configure One To Many Relationships in Entity Framework Core
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company);
}
try to use icollection instead list and map to explicit code
class Item{public virtual User User{get;set;};public Guid UserId{get;set;}}
modelbuilder.Entity<Item>.HasOne(s => s.User)
.WithMany(n => n.ItemsIds).HasForeignKey....

NotSupportedException: The type A cannot be mapped as definede. Table per Concrete (TPC) EF6

I have model like:
public abstract class Entity
{
public int Id { get; set; }
}
public abstract class Tree : Entity
{
public Tree() { Childs = new List<Tree>(); }
public int? ParentId { get; set; }
public string Name { get; set; }
[ForeignKey("ParentId")]
public ICollection<Tree> Childs { get; set; }
}
public abstract class Cat : Tree
{
public string ImageUrl { get; set; }
public string Description { get; set; }
public int OrderId { get; set; }
}
public class ItemCat : Cat
{
...
public virtual ICollection<Item> Items { get; set; }
}
and config classes:
public class CatConfig : EntityTypeConfiguration<Cat>
{
public CatConfig()
{
//properties
Property(rs => rs.Name).IsUnicode();
Property(rs => rs.ImageUrl).IsUnicode();
Property(rs => rs.Description).IsUnicode();
}
}
public class ItemCatConfig :EntityTypeConfiguration<ItemCat>
{
public ItemCatConfig()
{
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
}
}
and DbContext:
public class Db : IdentityDbContext<MehaUser>
{
public Db():base("Db")
{
}
public DbSet<ItemCat> ItemCats { get; set; }
}
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Configurations.Add(new ItemCatConfig());
base.OnModelCreating(mb);
}
but get:
System.NotSupportedException: The type 'ItemCat' cannot be mapped as defined because it maps inherited properties from types that use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change all types in the hierarchy to map inherited properties and to not use splitting
Update: I also Read this
Find the answer. just remove Map in ItemCatConfig Class.
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
In TPC abstract classes does not implement in db.
ItemCat inherit from abstract classes and it doesn't need to Map configuration explicitly.

Entity Framework CodeFirst many to many relationship with additional information

I have the following model :
class Contract
{
string ContractID{get;set;}
ICollection<Part> Parts{get;set;}
}
class Part
{
string PartID{get;set;}
ICollection<Contract> Contracts{get;set;}
}
the problem is that the relationship between Part and Contract also contains the following additional information :
class ContractParts
{
Contract{get;set;}
Part{get;set;}
Date{get;set;} //additional info
Price{get;set;} //additional info
}
How would I write the Entity Context for this ?
In such case you must model your entities this way:
public class Contract
{
public virtual string ContractId { get; set; }
public virtual ICollection<ContractPart> ContractParts { get; set; }
}
public class Part
{
public virtual string PartId { get;set; }
public virtual ICollection<ContractPart> ContractParts { get; set; }
}
public class ContractPart
{
public virtual string ContractId { get; set; }
public virtual string PartId { get; set; }
public virtual Contract Contract { get; set; }
public virtual Part Part { get; set; }
public virtual string Date { get; set; } //additional info
public virtual decimal Price { get; set; } //additional info
}
In derived context you must define:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ContractPart>()
.HasKey(cp => new { cp.ContractId, cp.PartId });
modelBuilder.Entity<Contract>()
.HasMany(c => c.ContractParts)
.WithRequired()
.HasForeignKey(cp => cp.ContractId);
modelBuilder.Entity<Part>()
.HasMany(p => p.ContractParts)
.WithRequired()
.HasForeignKey(cp => cp.PartId);
}
Perhaps a better way to do is this answer? Create code first, many to many, with additional fields in association table
It doesn't require fluent APIs and also sets up the PK on join table.

Categories

Resources