I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");
Related
I'm trying to make EfCore ignore a wrapping object similarly to the owned type concept.
How can I turn this object:
public class Entity
{
public Guid Id { get; set; }
public object SomeProperty { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<OtherItem> OtherItems { get; set; }
}
public class Item
{
public Entity Entity { get; set; }
public Guid EntityId { get; set; }
}
public class OtherItem
{
public Entity Entity { get; set; }
public Guid EntityId { get; set; }
}
Into this object
public class Entity
{
public Guid Id { get; set; }
public Aggregate Aggregate { get; set; } // This should not be mapped to the Database but only the properties
}
[Owned] // I think this is what i'm looking for
public class Aggregate
{
public object SomeProperty { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<OtherItem> OtherItems { get; set; }
public void SomeUsefulFunction()
{
// do Something Useful on the Aggregate
}
}
I would like EfCore to completely ignore the Aggregate object and threat his properties as if they were from the entity object. I thought the concept of owned entities was exactly this but I get the following error:
Unable to determine the relationship represented by navigation 'Aggregate.OtherItems' of type 'ICollection<OtherItem>'. Either manually configure the relationship, or ignore thi
s property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Like this?
public class Entity
{
public Guid Id { get; set; }
public Aggregate Aggregate => new Aggregate(this);
protected object SomeProperty { get; set; }
protected ICollection<Item> Items { get; set; }
protected ICollection<OtherItem> OtherItems { get; set; }
}
public class Aggregate
{
public object SomeProperty => _entity.SomeProperty;
public ICollection<Item> Items => _entity.Items;
public ICollection<OtherItem> OtherItems => _entity.OtherItems;
public Aggregate(Entity entity)
{
_entity = entity;
}
public void SomeUsefulFunction()
{
// do Something Useful on the Aggregate
}
}
public class SampleContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>().Ignore(c => c.Aggregate);
}
}
PS Just showing how to get, you can easly adapt it to use set too.
I suggest you to check out dotnet reference architecture https://github.com/dotnet-architecture/eShopOnContainers. There Ordering service is build using DDD approach - https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering.
It has good examples how to model Aggregates, Domain Events (Ordering.Domain folder) and also repository implementation along with configuration of EF entities (Ordering.Infrastructure folder).
Also there're couple of solution templates available to avoid typing boilerplate code :
https://github.com/jasontaylordev/CleanArchitecture
https://github.com/ardalis/CleanArchitecture
From microsoft docs :
Applications that follow the Dependency Inversion Principle as well as the Domain-Driven Design (DDD) principles tend to arrive at a similar architecture. This architecture has gone by many names over the years. One of the first names was Hexagonal Architecture, followed by Ports-and-Adapters. More recently, it's been cited as the Onion Architecture or Clean Architecture
I have tried several things before coming here, such as different model approach, annotation, declarations in DbContext, different fluent API usages but I can't seem to see what the issue is.
I have a YogaClass record but when I iterate over the subscriptions from a person, I have a subscription but no YogaClass (NULL) and yes I.Include(Person.Subscriptions) when querying the DB, I'm getting the subs but not the relational YogaClass/WorkShop associated with it.
In short, I have the following classes :
Subscription (base class)
public class Subscribtion
{
[Key]
public int SubscribtionID { get; set; }
public Person Person { get; set; }
public bool IsPayed { get; set; }
}
WorkshopSubscription (inherrits Subscription)
public class WorkshopSubscribtion : Subscribtion
{
[Key]
public int WorkshopSubscribtionID { get; set; }
public Workshop Workshop { get; set; }
}
YogaClassSubscription (inherrits Subscription)
public class YogaClassSubscribtion : Subscribtion
{
[Key]
public int YogaClassSubscribtionID { get; set; }
public YogaClass YogaClass { get; set; }
}
YogaClass (base class)
public class YogaClass
{
[Key]
public int YogaClassID { get; set; }
public List<Subscriptions> Subscriptions { get; set; }
}
WorkShop (base class)
public class WorkShop
{
[Key]
public int WorkShopID { get; set; }
public List<Subscriptions> Subscriptions { get; set; }
}
Now after I insert some records in the Seeder I have the following issue when I look into my DataBase:
Table Subscription : SubscriptionID:1 , WorkShop_WorkShopID:NULL , YogaClass_YogaClassID:NULL . (Why are they both NULL ?)
Table YogaClassSubscription : SubscriptionID:1 , YogaClassID:1
Same for workshop.
I don't get why the FK from Yoga & Workshop Subscription is NULL in Subscription table.
I have a DbSet declared in my Context and in modelBuilder fluent API method I have mapped both YogaClassSubscribtion & WorkShopSubscribtion to their own table.
public DbSet<Subscribtion> Subscribtions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WorkshopSubscribtion>().ToTable("WorkshopSubscribtions");
modelBuilder.Entity<YogaClassSubscribtion>().ToTable("YogaClassSubscribtions");
}
I think your problem comes from using the base class Subscription as the type of your generic lists Subscriptions. You should use the specific derived classes YogaClassSubscription and WorkShopSubscription:
public class YogaClass
{
[Key]
public int YogaClassID { get; set; }
public virtual List<YogaClassSubscription> Subscriptions { get; set; }
}
public class WorkShop
{
[Key]
public int WorkShopID { get; set; }
public virtual List<WorkShopSubscription> Subscriptions { get; set; }
}
This way EF knows about the relationship between WorkShops and WorkShopSubscriptions, and between YogaClasses and YogaClassSubscriptions.
Another thing that seems wrong: redefining the ID property and the Key annotations in your derived classes. You only need to define the ID in the base class. Remove those properties and their annotations. EF will create a foreign key with a one-to-one relationship between your base class table and the derived classes tables.
public class WorkshopSubscription : Subscription
{
public virtual Workshop Workshop { get; set; }
}
public class YogaClassSubscription : Subscription
{
public virtual YogaClass YogaClass { get; set; }
}
An advice: define your navigation properties as virtual, in order to allow EF to use proxies to track status changes in your entities, and also to allow the use of Lazy Loading.
I created an inheritance hierarchy after a few migrations. Now when I update the database using code first migrations, code-first is not automatically creating the discriminator field. I have since dropped the table and recreated it (using code-first migrations) without any luck. The only thing I can think of is that there are no additional "non-virtual" properties in the derived classes--the inheritance structure was created to enforce a business rule that only a certain derived type can have a relationship with another entity.
Base Type:
public abstract class Process
{
private ICollection<ProcessSpecification> _specifications { get; set; }
protected Process()
{
_specifications = new List<ProcessSpecification>();
}
public Int32 Id { get; set; }
public String Description { get; set; }
public Int32 ToolId { get; set; }
public virtual Tool Tool { get; set; }
public virtual ICollection<ProcessSpecification> Specifications
{
get { return _specifications; }
set { _specifications = value; }
}
}
Derived class (no different/unique scalar properties):
public class AssemblyProcess : Process
{
private ICollection<AssemblyProcessComponent> _components;
public AssemblyProcess()
{
_components = new List<AssemblyProcessComponent>();
}
public virtual ICollection<AssemblyProcessComponent> Components
{
get { return _components; }
set { _components = value; }
}
}
Another derived type
public class MachiningProcess : Process
{
private ICollection<MachiningProcessFeature> _features;
public MachiningProcess()
{
_features = new List<MachiningProcessFeature>();
}
public virtual ICollection<MachiningProcessFeature> Features { get { return _features; } set { _features = value; } }
}
Is code-first not adding the discriminator column in the database because it doesn't see any differences between the derived classes (because of there not being any unique "non-virtual" properties)? If so, how do I get around this? If not, what are some reasons why code-first would not automatically create the discriminator column in the database? I have another TPH structure that works exactly the way it's supposed to.
DbContext:
public LineProcessPlanningContext()
: base("LineProcessPlanning")
{
}
public DbSet<Component> Components { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<OperationDefinition> OperationDefinitions { get; set; }
public DbSet<PartDesign> PartDesigns { get; set; }
public DbSet<Process> Processes { get; set; }
public DbSet<ProcessPlan> ProcessPlans { get; set; }
public DbSet<ProcessPlanStep> ProcessPlanSteps { get; set; }
public DbSet<ProductionLine> ProductionLines { get; set; }
public DbSet<StationCycleDefinition> StationCycleDefinitions { get; set; }
public DbSet<StationCycleStep> StationCycleSteps { get; set; }
public DbSet<StationDefinition> StationDefinitions { get; set; }
public DbSet<UnitOfMeasurement> UnitsOfMeasurement { get; set; }
public DbSet<Tool> Tools { get; set; }
I also tried creating "dummy" properties that are unique to each derived type. Code migrations added the new properties as columns to the table, but the migration did not create a discriminator column.
I figured out the cause of this in my situation, same as yours. The base class is abstract, therefore EF won't create a TPH table for that class since it can't be instantiated. As a result of the abstract base class, EF will create tables for each of the derived classes, and therefore no need for a discriminator column.
In my case, it was acceptable to remove abstract from the base class. Once I did this, EF's TPH worked as expected.
I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();
I just started prototyping our existing object model in entity framework/code first and hit my first snag. Unfortunately the documentation for this seems to be very scarce.
My key is not a primitive but an object that wraps a primitive. How do I get this to work with EF/Code first:
public class EFCategoryIdentity
{
public string IdentityValue { get; private set; }
public EFCategoryIdentity(string value)
{
IdentityValue = value;
}
}
public class EFCategory
{
[Key]
public EFCategoryIdentity CategoryIdentity { get; set; }
public string Name { get; set; }
public virtual ICollection<EFProduct> Products { get; set; }
}
public class EFProduct
{
[Key]
public int ProductId { get; set; }
public string Name { get; set; }
public virtual EFCategory Category { get; set; }
}
What do I need to put here to make this work?
public class MyTestContext : DbContext
{
public DbSet<EFCategory> Categories { get; set; }
public DbSet<EFProduct> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<EFCategory>()
.// Help!
}
}
Thanks!
Entity framework can use only primitive types as keys. Every time you wrap some mapped property to separate type you are creating complex type. Complex types have some limitation
They cannot be keys
They cannot contain keys
They cannot contain navigation properties
etc.