I have system being developed for an HR system. There are Accountant employees and Programmer employees. For the first month of joining the company, the employee is not given any role. One employee can be an Accountant and a programmer at the same time. I have a design shown by the following code.
Now, I need to enhance the system by implementing a new functionality:
Terminate all Accountants. (Terminate means set status of employee as IsActive = false). The issue is I cannot set all accountants directly as inactive without checking. I need to check whether he has got any other role.
How to remodel these classes in order to do the terminate function more natural OO ?
UPDATE
I am looking for an answer that has EF Database First solution model and database schema for #AlexDev answer.
C# Code
List<Accountant> allAccountants = Get All accountants from database
public class Employee
{
public int EmpID { get; set; }
public DateTime JoinedDate { get; set; }
public int Salary { get; set; }
public bool IsActive { get; set; }
}
public class Accountant : Employee
{
public Employee EmployeeData { get; set; }
}
public class Programmer : Employee
{
public Employee EmployeeData { get; set; }
}
#AlexDev Answer
public class Employee
{
...
IList<Role> Roles;
bool isActive;
public void TerminateRole(Role role)
{
Roles.Remove(role);
if(Roles.Count == 0)
{
isActive = false;
}
}
}
public class Role
{
abstract string Name { get;}
}
public class ProgrammerRole : Role
{
override string Name { get { return "Programmer"; } }
}
REFERENCE
DDD Approach to Access External Information
Prefer composition over inheritance?
Inheritance vs enum properties in the domain model
Entity Framework: Get Subclass objects in Repository
To use the structure you are using you would need multiple inheritance for someone who is an accountant and a programmer, besides new roles might be added to the system, and that doesn't exist in C#. You should consider a different design. One possibility:
public class Employee
{
...
IList<Role> Roles;
bool isActive;
public void TerminateRole(Role role)
{
Roles.Remove(role);
if(Roles.Count == 0)
{
isActive = false;
}
}
}
public class Role
{
abstract string Name { get;}
}
public class ProgrammerRole : Role
{
override string Name { get { return "Programmer"; } }
}
Then you can subclass Role for each type, and you can decide to terminate just one role, or all of them.
I'm writing a new answer since from the schema you added to the question I'm assuming you won't be subclassing Role. Also if you are using NHibernate don't forget to use public virtual properties.
public class Employee
{
...
public virtual IList<Role> Roles { get; set; }
public virtual bool isActive { get; set; }
public virtual void TerminateRole(Role role)
{
Roles.Remove(role);
if(Roles.Count == 0)
{
isActive = false;
}
}
}
public class Role
{
public virtual int RoleID { get; set; }
public virtual string Name { get; set; }
}
And mappings:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.EmpId);
Map(x => x.JoinedDate)
Map(x => x.Salary);
Map(x => x.IsActive);
HasManyToMany(x => x.Roles).Cascade.AllDeleteOrphan().Table("EmployeeRole")
}
}
public class RoleMap : ClassMap<Role>
{
public RoleMap()
{
Id(x => x.RoleID);
Map(x => x.RoleName);
}
}
public abstract class AbstractEmployee
{
...
public abstract bool IsActiveAccountant { get; set; }
public abstract bool IsActiveProgrammer { get; set; }
public bool IsActive() { get { return bitwise or of all roles; } }
}
public class NewEmployee : AbstractEmployee
{
...
public override bool IsActiveAccountant { get; set; }
public override bool IsActiveProgrammer { get; set; }
}
public class Programmer : AbstractEmployee
{
...
public override bool IsActiveAccountant { get; set; }
public override bool IsActiveProgrammer { get; set; }
}
Cons:
with every new system-wide role added you have to modify classes
Pros:
you dont need to search for accountants
programmers can have empty implementation of IsActiveAccountant, because this role is inactive for them anyway
NewEmployee can have many roles at the same time
If overhead from introducing new roles is significant, I would stick with searching
From my answer in Prefer composition over inheritance?
I will first start with the check - whether there exists an "is-a" relationship. If it exists I usually check the following:
Whether the base class can be instantiated. That is, whether the base class can be non-abstract. If it can be non-abstract I usually prefer composition
E.g 1. Accountant is an Employee. But I will not use inheritance because a Employee object can be instantiated.
E.g 2. Book is a SellingItem. A SellingItem cannot be instantiated - it is abstract concept. Hence I will use inheritacne. The SellingItem is an abstract base class (or interface in C#)
Related
So I've implement Table Per concrete Class to deal with an inheritance hierarchy, but I'm having trouble with Navigation properties.
My Model is structure as follows:
I have an abstract BaseEntity class, with Multiple derived classes, so:
public abstract class BaseEntity
{
public virtual ICollection<Comment> Comments { get; set; }
public EntitytType EntitytType { get; set; }
}
public class FirstDerivedEntity : BaseEntity
{
//EntitytType == First;
}
public class SecondDerivedEntity : BaseEntity
{
//EntitytType == Second;
}
public class Comment
{
public long BaseEntityId { get; set; }
public EntitytType BaseEntityType { get; set; }
}
public enum EntitytType
{
First,
Second
}
The Comments navigation property here doesn't work because each derived(Concrete) class has it's own set of Ids. In my mind the EntityType column in each Table would serve as some sort of Discriminator, but I don't know how to tell EF to use it as such.
Any help would be greatly appreciated!
The solution in order for TPC to work would be the Ids in the derived classes to be unique not only in their table but in both tables.
You can use a database solution like an auto increment primary key with different initial seeds or GUID keys like the ones in SQL server.
Another approach would be to generate unique GUID keys in you application code.
You can see same sample code of how to model the entities below :
namespace tpc_so
{
public class tpc_soContext : DbContext
{
public tpc_soContext()
{
}
public DbSet<BaseEntity> BaseEntities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseEntity>().HasKey(b => b.BaseEntityId);
modelBuilder.Entity<BaseEntity>()
.Property(b => b.BaseEntityId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<FirstDerivedEntity>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("FirstDerivedEntities");
});
modelBuilder.Entity<SecondDerivedEntity>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("SecondDerivedEntities");
});
modelBuilder.Entity<Comment>().ToTable("Comments");
base.OnModelCreating(modelBuilder);
}
}
public abstract class BaseEntity
{
public Guid BaseEntityId { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class FirstDerivedEntity : BaseEntity{}
public class SecondDerivedEntity : BaseEntity{ }
public class Comment
{
public long CommentId { get; set; }
public Guid BaseEntityId { get; set; }
public string Text { get; set; }
}
}
And to create some entities use the code below :
using (var ctx = new tpc_soContext())
{
ctx.BaseEntities.Add(new FirstDerivedEntity()
{
BaseEntityId = Guid.NewGuid(),
Comments = new List<Comment>() { new Comment() { Text = "First Derived Comment" } }
});
ctx.BaseEntities.Add(new SecondDerivedEntity()
{
BaseEntityId = Guid.NewGuid(),
Comments = new List<Comment>() { new Comment() { Text = "Second-Derived Comment" } }
});
ctx.SaveChanges();
}
Some good sources for TPC would be here :
[Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)]
[Entity Framework - Table Per Concrete]
I have an abstract object which have two list of abstract objects in it. The Model is being created and the Database looks fine, but I am unable to make the queries I would expect.
The Data model looks something like this
public abstract class Vehicle
{
protected Vehicle()
{
this.CrashIncidents = new List<Incident>();
this.SpeedingIncidents = new List<Incident>();
}
[Key]
public int Id { get; set; }
public virtual ICollection<Incident> CrashIncidents { get; set; }
public virtual ICollection<Incident> SpeedingIncidents { get; set; }
}
public class Car : Vehicle
{
public string Color { get; set; }
}
public class Lorry : Vehicle
{
public int MaxCarryWeight { get; set; }
}
public abstract class Incident
{
[Key]
public int Id { get; set; }
public virtual ICollection<Incident> VehicleCrashIncidents { get; set; }
public virtual ICollection<Incident> VehicleSpeedingIncidents { get; set; }
}
public class CrashIncident : Incident
{
public string Severity { get; set; }
}
public class SpeedingIncident : Incident
{
public string MPHRegistered { get; set; }
}
Any my OnModelCreating in the Context class looks something like this
modelBuilder.Entity<Vehicle>().HasMany<Incident>(o => o.CrashIncident).WithMany(a => a.VehicleCrashIncidents).Map(m => m.MapLeftKey("Id").MapRightKey("VehicleCrashIncidentId").ToTable("VehicleCrashIncident"));
modelBuilder.Entity<Vehicle>().HasMany<Incident>(o => o.SpeedingIncident).WithMany(a => a.VehicleSpeedingIncidents).Map(m => m.MapLeftKey("Id").MapRightKey("VehicleSpeedingIncidentId").ToTable("VehicleSpeedingIncident"));
modelBuilder.Entity<CrashIncident>().ToTable("CrashIncident");
modelBuilder.Entity<SpeedingIncident>().ToTable("SpeedingIncident");
However I am unable to query things like: Get all Vehicles (or concrete classes) with an Incident of Severity of X i.e. something like this:
var problems = context.Vehicle.Where(x => x.CrashIncidents.Any(y => y.Severity == "High");
The problem is the last part of the query (in the y-part) where I am not able to select Severity, only the Properties of the Abstract class is visible. I am unable to determine (and thus Google) if the problem lies with my Data model or with my Query.
Inspired by this:
Issue with many-to-many relationship + TPH inhertitance in Entity Framework 6
I got this working. I removed the specific virtual parts from the Abstract part of my model like this:
public abstract class Vehicle
{
protected Vehicle()
{
this.Incidents= new List<Incident>();
}
[Key]
public int Id { get; set; }
public virtual ICollection<Incident> Incidents{ get; set; }
}
And changed the navigation properties to
public abstract class Incident
{
[Key]
public int Id { get; set; }
public virtual ICollection<Incident> VehicleIncidents { get; set; }
}
In the OnModelCreating I could remove the two "modelBuilder.Entity().HasMany" lines. In the end I could execute this query:
var problems = context.Vehicle.Where(x => x.Incidents.OfType<CrashIncidents>.Any(y => y.Severity == "High");
I must admit I am unsure of whether I tried that specific query before, so I am not sure if it is the changes in my Data model that allowed me to make that query or it was possible all along and I just didn't know it.
I'm working with Entity Framework 6.0.2 Code First with Sql Server.
I have a base class called Entity and since we can't extend enums I need to redefine a property type for another class called Company, so I'm using the new keyword to hide the base property and redefine it.
public interface IEntity
{
Guid Id { get; set; }
State State { get; set; }
}
public abstract class Entity : IEntity
{
public Guid Id { get; set; }
public State State { get; set; }
}
public enum State
{
Inactive = 0,
Active = 1
}
public class Company : Entity
{
public new CompanyState State { get; set; }
public string SomeOtherProp { get; set; }
}
public enum CompanyState
{
Inactive = 0,
Active = 1,
Extra = 2
}
The problem I get is when Entity Framework is trying to create the DbContext it crashes with this error: "The item with identity 'State' already exists in the metadata collection. Parameter name: item"
I have a workaround: I could change the State propery in Entity class to int and cast the appropriate enum to int, but I think I'll lose type safety / restriction that the enums have.
I'd like to change the metadata info to avoid this error but I don't know how.
This guy here found a solution for some similar problem.
Neither your, nor his solution is nice. It is and remains a hack.
I would go with the solution you already mentioned. Change the state to stateId. And add a State Property to your Entity:
public State State {get {return (State)stateId;}
In your Company override this Property with new:
public new CompanyState State {get {return (CompanyState )stateId;}
But I think best solution would be, to change your inheritance hierarchy. I think either your IEntity should not have a state, or your company should not inherit from Entity.
Just for exposing another way you could also use this model with an hidden backing field and not mapped states
public interface IEntity
{
int Id { get; set; }
State State { get; set; }
}
public abstract class Entity : IEntity
{
protected int InternalState { get; set; }
public int Id { get; set; }
[NotMapped]
public State State
{
get { return (State) InternalState; }
set { InternalState = (int) value; }
}
// Entity is not a POCO class because of this :(
// If we want to hide InternalState this is the only way to map it
public class EntityMap : EntityTypeConfiguration<Entity>
{
public EntityMap()
{
// Properties
Property(t => t.InternalState)
.HasColumnName("State");
}
}
}
public enum State
{
Inactive = 0,
Active = 1
}
public class Company : Entity
{
[NotMapped]
public new CompanyState State
{
get { return (CompanyState)InternalState; }
set { InternalState = (int)value; }
}
[MaxLength(50)]
public string SomeOtherProp { get; set; }
}
public class Employee : Entity
{
[MaxLength(50)]
public string SomeOtherProp { get; set; }
}
public enum CompanyState
{
Inactive = 0,
Active = 1,
Extra = 2
}
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");
I have a POCO class that has two one-way unary relationships with another class, both classes share an ancestor. The names of the foreign keys in the generated schema do not reflect the property names. (Properties MainContact and FinancialContact give PersonId and PersonId1 field names).
How can I influence schema generation to generate database column names that match the property names?
The model looks like this:
The code looks like this:
public class CustomerContext: DbContext
{
public DbSet<Organisation> Organisations { get; set; }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
DbDatabase.SetInitializer(new DropCreateDatabaseAlways<CustomerContext>());
}
}
public abstract class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person : Customer
{
public string Email { get; set; }
}
public class Organisation : Customer
{
public Person FinancialContact { get; set; }
public Person MainContact { get; set; }
}
The schema looks like this:
Answer from druttka
druttka's answer below did the job and it's nice to know that it's a CTP5 bug that's behind this. EF also needs the cascade behaviour to be specified and I've used the fluent API to do this following the example in the link given by druttka. Some more good reading from Morteza Manavi here.
The code now is this:
public class CustomerContext : DbContext
{
public DbSet<Organisation> Organisations { get; set; }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
DbDatabase.SetInitializer(new DropCreateDatabaseAlways<CustomerContext>());
builder.Entity<Organisation>()
.HasRequired(p => p.MainContact)
.WithMany()
.HasForeignKey(p => p.MainContactId)
.WillCascadeOnDelete(false);
builder.Entity<Organisation>()
.Property(p => p.MainContactId)
.HasColumnName("MainContact");
builder.Entity<Organisation>()
.HasRequired(p => p.FinancialContact)
.WithMany()
.HasForeignKey(p => p.FinancialContactId)
.WillCascadeOnDelete(false);
builder.Entity<Organisation>()
.Property(p => p.FinancialContactId)
.HasColumnName("FinancialContact");
}
}
public abstract class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person : Customer
{
public string Email { get; set; }
}
public class Organisation : Customer
{
public Person FinancialContact { get; set; }
public int FinancialContactId { get; set; }
public Person MainContact { get; set; }
public int MainContactId { get; set; }
}
Which now gives the far more suitable database:
EF Code First uses, by default, convention over configuration. However, you can set explicit alternatives by overriding DbContent.OnModelCreating. Many examples here, courtesy of ScottGu.
EDIT
So in CTP5, MapSingleType went away as described here. The following works for simple string properties, but not for your Organisation to Person relationships. I'm curious and plan to keep looking at it, but in the meantime, maybe this will get your started or someone else can complete the answer.
public class Person : Customer
{
[Column(Name="EmailAddress")]
public string Email { get; set; }
}
EDIT 2
Ok, this gets it. Found the answer here. Disclaimer: I've only verified that the database schema is created as expected. I have not tested that seeding data or further CRUD operations work as expected.
public class Organisation : Customer
{
[Column(Name = "FinancialContact")]
public int? FinancialContactId { get; set; }
[ForeignKey("FinancialContactId")]
public Person FinancialContact { get; set; }
[Column(Name = "MainContact")]
public int? MainContactId { get; set; }
[ForeignKey("MainContactId")]
public Person MainContact { get; set; }
}