An exception is thrown while running add-migration (EF core 3.1.1):
CLR property 'DiscriminatorLevel2Id' cannot be added to entity type 'CustomerBase' because it is declared on the CLR type 'InternalCustomer'
The following Image shows the needed hierarchy (briefly):
Mapping looks like:
// Discriminator (Level 1)
modelBuilder.Entity<CustomerBase>()
.HasDiscriminator(b => b.CustomerTypeId)
.HasValue<InternalCustomer>((int)CustomerType.Internal)
.HasValue<ExternalCustomer>((int)CustomerType.External);
// Discriminator (Level 2)
modelBuilder.Entity<InternalCustomer>()
.HasDiscriminator(b => b.DiscriminatorLevel2Id)
.HasValue<VIPCustomer>((int)DiscriminatorLevel2.VIP)
.HasValue<RegularCustomer>((int)DiscriminatorLevel2.Regular);
Is "Multilevel Inheritance TPH" supported on Entity Framework Core 3.1.1?
It's possible, but with single shared discriminator at the root abstract level containing values for all possible creatable (non abstract) direct or indirect derived entities.
Applying to your sample requires removing the DiscriminatorLevel2 property (column), removing Internal from CustomerType enum (assuming InternalCustomer is abstract) and merging Regular and VIP into it, e.g. something like this:
Model:
public abstract class CustomerBase
{
public int Id { get; set; }
public string Name { get; set; }
public int CustomerTypeId { get; set; }
}
public abstract class InternalCustomer : CustomerBase
{
public decimal Points { get; set; }
}
public class RegularCustomer : InternalCustomer
{
public int PartnerId { get; set; }
}
public class VIPCustomer : InternalCustomer
{
public string CardNo { get; set; }
}
public class ExternalCustomer : CustomerBase
{
}
public enum CustomerType { External, Regular, VIP }
Configuration:
modelBuilder.Entity<CustomerBase>()
.HasDiscriminator(b => b.CustomerTypeId)
.HasValue<ExternalCustomer>((int)CustomerType.External)
.HasValue<VIPCustomer>((int)CustomerType.VIP)
.HasValue<RegularCustomer>((int)CustomerType.Regular);
modelBuilder.Entity<InternalCustomer>();
When you want to query InternalCustomer derived entities, you could use db.Set<InternalCustomer>() or db.Set<CustomerBase>().OfType<InternalCustomer>() and EF Core will apply filter similar to t.CustomerTypeId IN (1,2), i.e. the IN clause will contain list of discriminator values for all final entities derived from InternalCustomer.
Related
I am working on a project in Entity Framework Core which uses POCOs to connect to a database. However, these POCOs cannot convey intent, which is necessary because of a role-based permission system. I need some way to convey roles alongside my POCO.
However, some of the requirements of the project make it difficult to use subclasses with additional fields to do this. Thus, I have been using interfaces and partial classes, as under normal circumstances, I can use an interface to control which parts of the class are being recognized.
For example, in following code I have a guest entity, with the role of who is editing the entity. Only the fields Id, Name, and Confirmed exist as columns within the database. Under most circumstances, I can cast to an IGuest to remove the "MyRole" field.
public partial class Guest : IGuest
{
public int Id { get; set; }
public string Name { get; set; }
public bool Confirmed { get; set; }
}
public partial class Guest : IRole
{
public string MyRole { get; set; }
}
public interface IGuest
{
public int Id { get; set; }
public string Name { get; set; }
bool Confirmed { get; set; }
}
public interface IRole
{
public string MyRole { get; set; }
}
However, if I try to create a DbSet of guests, it attempts to find a matching column for MyRole. I tried changing the following sets of code
public virtual DbSet<Guest> Guest { get; set; } = null!;
and
modelBuilder.Entity<Guest>(entity =>
{
entity.HasKey(e => e.Id);
});
into the following:
public virtual DbSet<IGuest> Guest { get; set; } = null!;
and
modelBuilder.Entity<IGuest>(entity =>
{
entity.HasKey(e => e.Id);
});
This results in an error
ArgumentException: The specified type '...IGuest' must be a non-interface reference type to be used as an entity type.
Is it possible to use an interface as part of a DbSet / entity to hide the MyRole field (and any others not in the IGuest interface) from Entity Framework so that it does not attempt to map it to a column?
I am having trouble with setting up a many-to-many join table with EF core if one side of the join table is a derived class in table-per-hierarchy set up.
Here's the set up:
class Chore
{
Guid Id;
}
class LaundryChore : Chore
{
// PROBLEMATIC
List<Clothing> ManyClothing;
}
class FoldingChore : Chore
{
Clothing SingleClothing;
}
class Clothing
{
Guid Id;
// PROBLEMATIC
List<Chore> Chores;
}
I have the TPH set up with discriminator and that all works fine. IF the ManyClothing field was on the Chore class then I can just do:
builder.Entity<Clothing>().HasMany(clothing => clothing.Chores)
.WithMany(chore => chore.ManyClothing);
and this works as expected.
But since ManyClothing field was on the LaundryChore class, I would get DNE error with above.
I tried switching the direction:
builder.Entity<LaundryChore>().HasMany(chore => clothing.ManyClothing)
.WithMany(clothing => clothing.Chores);
and I get a casting error:
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'
If I change to:
class Clothing
{
Guid Id;
List<LaundryChore> Chores;
}
Then the error I get is:
The filter expression ... cannot be specified for entity type 'LaundryChore'. A filter may only be applied to the root entity type 'Chore'
Any guidance would be appreciated - thanks!
A navigation properties can only participate in a single relationship.
It isn't a limitation type, but a limitation by relation. For example, if you have only the Chore and Clothing classes :
public class Chore
{
public int Id { get; set; }
public List<Clothing> ManyClothingToLaundry { get; set; }
public Clothing SingleClothingToFolding { get; set; }
}
public class Clothing
{
public int Id { get; set; }
public List<Chore> Chores { get; set; }
}
Now, you want add a Chore to a Clothing :
clothing.Chores.Add(chore);
Do this add a chore to laundry or to folding? EF Core can't know in this case.
In your specific case, EF Core could detect the relationship from the type, but this functionality is not implemented.
If Clothing has two distinct relationship, then Clothing need two navigation properties :
public class Clothing
{
public int Id { get; set; }
public List<Chore> FoldingChores { get; set; }
public List<Chore> LaundryChores { get; set; }
public IEnumerable<Chore> Chores => FoldingChores.Union(LaundryChores);
}
...
protected override void OnModelCreating(ModelBuilder builder)
{
...
builder.Entity<Clothing>().Ignore(c => c.Chores);
builder.Entity<Clothing>()
.HasMany<LaundryChore>(nameof(Clothing.LaundryChores))
.WithMany(chore => chore.ManyClothing);
builder.Entity<Clothing>()
.HasMany<FoldingChore>(nameof(Clothing.FoldingChores))
.WithOne(chore => chore.SingleClothing);
}
Clothing.FoldingChores is a collection of the base entity Chore for the example, but it can be directly a collection of the finale type FoldingChore. Idem with Clothing.LaundryChores :
public class Clothing
{
public int Id { get; set; }
public List<FoldingChore> FoldingChores { get; set; }
public List<LaundryChore> LaundryChores { get; set; }
public IEnumerable<Chore> Chores => FoldingChores.Cast<Chore>().Union(LaundryChores.Cast<Chore>());
}
In EF Core 3.1 I am attempting to implement a Concurrency Token for my base class. I run into problems for entities stored in shared tables of which a derived class owns an entity. When the derived class holds a single property, it is advised to create this property as a shadow property on the base class. However with the property that represents an owned type I don't know how I can do this. I am trying to avoid putting in a shadow property for every single property in the owned type.
The class from which all entities are derived:
public class EntityClass
{
///...
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
}
An example of a base class and the derived class that holds an additional owned type:
public class Transaction : EntityClass
{
///...
public Company Counterparty { get; set; }
public Currency TransactionCurrency { get; set; }
}
public class CashTransaction : Transaction
{
///...
public Currency BankAccountCurrency { get; set; }
}
Currency is an owned model and its properties are stored in the table related to Transaction:
[Owned, ComplexType]
public class Currency
{
///...
public string CurrencyName { get; set; }
public string CurrencySymbol { get; set; }
}
When I add the migration the following error shows:
Entity type 'CashTransaction.BankAccountCurrency#Currency' doesn't contain a property mapped to the store-generated concurrency token column 'ConcurrencyToken' that is used by another entity type sharing the table 'Transaction'. Add a store-generated property mapped to the same column to 'CashTransaction.BankAccountCurrency#Currency'. It can be in shadow state.
I tried to configure the shadow property with a few tries, for example:
modelBuilder.Entity<Transaction>().OwnsOne<Currency>("BankAccountCurrency");
EDIT:
Below does not work: it leads to issues with change tracking on derived class.
For a while I thought below would work: create a private property of the owned entity, and then configure this in the modelBuilder call:
public class Transaction : EntityClass
{
///...
public Company Counterparty { get; set; }
public Currency TransactionCurrency { get; set; }
private Currency BankAccountCurrency { get; set; }
}
modelBuilder.Entity<Transaction>().OwnsOne(typeof(Currency), nameof(CashTransaction.BankAccountCurrency));
How would you migrate this Car Class to a database using the Entity Framework?
I have a Main car class (which inherits from a Car Interface Class) with one Generic property called Part
public class Car<Parts> : GenericCarInterface
{
public Parts Part { get; set; }
}
Then I have several enums representing different parts that can be passed into the Generic Parts property of the Car Class
public enum VokswagenParts
{
VWWheels,
VWLights,
VWEngine
}
public enum TyotaParts
{
ToyotaSpoiler,
ToyotaAirFilter,
ToyotaBrakes
}
public enum FiatParts
{
FiatExhaust,
FiatSuspension,
FiatCVOuter
}
I envisage setting up an ApplicationDbContext as follows but the EF does not know what to make of the Part property
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"ConnectionStringHere");
}
public DbSet<GenericCarInterface> Cars { get; set; }
}
Any Ideas on the ways that this can be accomplished using Entity Framework Core?
Looking at your enums, it looks like each part has a unique name. if you don't mind whether your parts are grouped or not, you could put all your parts together in single enum.
public enum Parts {
VWWheels,
VWLights,
VWEngine,
ToyotaSpoiler,
ToyotaAirFilter,
ToyotaBrakes,
FiatExhaust,
FiatSuspension,
FiatCVOuter
}
then in your Car class you can do this
public Parts Parts { get; set; }
EF should be able to handle the rest.
I think i may have figured out a solution.
If i can create a separate class in my repository to hold the enum number values and the enum type names:
public class Car
{
public Guid CarId { get; set; }
public Part Part { get; set; }
}
public class Part
{
public Guid CarId { get; set; }
public Guid PartId { get; set; }
public int Enum { get; set; }
public string EnumName { get; set; }
}
Then I can persist that standard class to the database then simply re-compile the code back to the original Car class format just before transferring between layers
I might create a separate enum for the part names just to keep it strongly typed but I like the idea of having the names persisted not just enum numbers
I am trying to do something like this :
public class TrackerContext : DbContext
{
public bool TrackNewValues { get; set; }
public TrackerContext(bool trackNewValues = false)
: base()
{
TrackNewValues = trackNewValues;
}
public TrackerContext(string connectinString, bool trackNewValues = false)
: base(connectinString)
{
TrackNewValues = trackNewValues;
}
public DbSet<AuditLog<string>> AuditLog { get; set; }
public DbSet<AuditLogChild> LogChildren { get; set; }
}
public class AuditLog<UserIdOrUserNameColumnType>
{
public AuditLog()
{
Children = new List<AuditLogChild>();
}
[Key]
public Guid AuditLogID { get; set; }
public UserIdOrUserNameColumnType UserId { get; set; }
[Required]
public DateTimeOffset EventDateUTC { get; set; }
}
But I guess DbSet<AuditLog<string>> is not supported. I get this error:
Additional information: The type 'TrackerEnabledDbContext.AuditLog`1[System.String]' was not mapped.
Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation.
Verify that the type was defined as a class, is not primitive or generic, and does not inherit from EntityObject.
Is there any ways I can implement public DbSet<AuditLog<string>> AuditLog { get; set; } ?
You cannot map the generic type because Entity Framework simply doesn't support generic Entity types.
When using the EF Code-First approach you need to remember that you should model your POCO classes within the constraints that allow Entity Framework to create POCO proxies.
This means, shortly speaking that such a class:
Should not contain any attributes
Should not be generic
Should be public
Must not be sealed
Must not be abstract
Must have a public or protected constructor that does not have parameters
I have been using generic classes with success in Entity Framework.
If you declare your class and DbSet the following way it will work.
public class AuditLogString : AuditLog<String>{}
public DbSet<AuditLogString> AuditLogStrings { get;set;}
[Update]
I have not used this method recently and in the light of the comments on this answer I suggest Pawel's answer instead.
However I have not deleted this answer since I was able to use the method.