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));
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?
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
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.
I have an object that contains an attribute with the type of another object, which I want to treat as Complex Type.
public class Location : IModule
{
public string Id { get; set; }
public Coordinate Coordinate { get; set; }
}
[ComplexType]
public class Coordinate
{
public string Latitude { get; set; }
public string Longitude { get; set; }
}
While adding a migration, I ran into the problem that a primary key is required (exactly what I want to prevent).
The entity type Coordinate requires a primary key to be defined.
EDIT
For performance reasons I want the properties being stored as Coordinate_Latitude and Coordinate_Longitute instead of having a reference to another table.
Based on this question (How do I implement a simple "complex type" in Entity Framework Core 2/C#?), I found the answer: Owned entity types do the trick.
public class Location : IModule
{
public string Id { get; set; }
public Coordinate Coordinate { get; set; }
}
[Owned]
public class Coordinate
{
public string Latitude { get; set; }
public string Longitude { get; set; }
}
This creates a table containt the attributes Id, Coordinate_Latitued, Coordinate_Longitude.
You need to define a key, to make it works. This is how the Entity Framework works, Entity Framework needs to know the key to keep track on the object when you make an update or delete operation. Just if you don't want to manually insert it, you can declare it as an identity column to auto increment it. Something like this:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CoordinateKey { get; set; }
Or with Fluent-API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Coordinate>().HasKey(u => u.CoordinateKey);
modelBuilder.Entity<Coordinate>().Property(c => c.CoordinateKey)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
Edit: It seems you want to treat the Coordinate class as a not mapped class. You can use NotMapped attribute. Have a look at the following question to know how:
Entity Framework code first: How to ignore classes
I am using EF 6 and have following
public class Title : EntityBase
{
public short Id { get; set; }
public string TitleName { get; private set; }
}
public abstract class EntityBase
{
public DateTime CreatedDate { get; set; }
}
And DbContext contains
public DbSet<Title> Titles { get; set; }
and also
modelBuilder.Entity<Title>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Titles");
});
But while trying to access Titles, I get "The entity type EntityBase is not part of the model for the current context." After I run Update-Database in NuGet console, I see that table 'Titles' is created in database with all fields (id,titlename, createddate) as expected. I don't want to add EntityBase db set in DbContext if possible. Kindly help me to find a solution.
MapInheritedProperties is used to model a table-per-concrete type (TPC) mapping, a complete table for each class. You can do this, but then you have to configure the base class as well.
modelBuilder.Entity<EntityBase>();
However, I'd rather remove the MapInheritedProperties statement and map each class individually. If you map a TPC mapping you'll never be able to map other inheritance associations between other classes of your model.