I have an employee table that self references to determine organization structure. I'm having some trouble trying to set this up using Code-First (POCO) fluently.
An employee record has both a "Position" field and a "ReportsTo" field and neither of the columns are the primary key (employee.id).
An employee with a "ReportsTo" value of "08294" , is an employee of a direct report of an employee with "Position" value of "08294".
Can anyone offer up some info on how to set this up using EF code first, fluently...is it possible?
I tried the code below and am getting error:
Employee_Employees_Source_Employee_Employees_Target: : The types of
all properties in the Dependent Role of a referential constraint must
be the same as the corresponding property types in the Principal Role.
The type of property 'ReportsTo' on entity 'Employee' does not match
the type of property 'Id' on entity 'Employee' in the referential
constraint 'Employee_Employees'.
Employee.cs
public class Employee
{
public int Id { get; set; } //pk
public string Position { get; set; } // i.e. 06895
public string ReportsTo{ get; set; } // i.e. 08294
public virtual Employee Supervisor { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
DbContext
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.ReportsTo);
I think more than anything, I would like to keep the POCO free of EF "stuff" and be able to do something like:
employee.IsSupervisor(); // based on child employee count.
The issue is in the relationship configuration. If you want to configure your one to many relation without using a FK, you could do this:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor);
Now if you want to use a FK property, then add this property to your model class:
public class Employee
{
//...
public int SupervisorId { get; set; }
}
And map your relationship this way:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.SupervisorId);
To resolve your issue related with ReportTo and Position properties,I think you should handle that logic in your code. If you want to know if an Employee is a supervisor based on the count of Employees property, you could use a NotMapped property:
public class Employee
{
[NotMapped]
public bool IsSupervisor
{
get
{
return Employess.Count>0
}
}
}
You can do the same using Fluent Api:
modelBuilder.Entity<Employee>().Ignore(e => e.IsSupervisor);
PS: Remember initialize Employees in your class'constructor.
The error you get is because it is trying to map a PK of int type to a FK of string type. User int for all of your key fields.
Then, you need to declare your OnModelBuilding like this:
modelBuilder.Entity<Employee>()
.HasOptional(e => e.Supervisor)
.WithMany()
.HasForeignKey(s => s.ReportsTo);
To get something like IsSupervisor() you can take advantage of partial classes. Create another class file which is a public partial class Employee (and modify your original one to be partial), then in your new file you will add a property that does whatever you want, and decorate it with [NotMapped] attribute. Yours will probably look something like public bool IsSupervisor {get { return (Employees == null) ? false : true; } set {} } The new partial class is where you can do whatever you want for the POCO without changing the EF class (make sure you use [NotMapped] though).
Related
I have a typical master/detail (User / Settings table) table schema (SQL Server) and setup Entity Framework using Fluent API to work with those tables.
I define this as an independent association, so the UserProfileSetting class doesn't include the UserId property, but I understand is correctly mapped in the configuration.
Well, my problem is that when one item of Settings is updated for a profile, at the database level that settings is updated for all users. Basically USER_ID is not considered.
The SQL query produced is this:
UPDATE [dbo].[T_USERPROFILE_SETTING]
SET [VALUE] = #0
WHERE ([KEY] = #1)
Any idea what could be wrong? I guess that if I finally add the UserId property to UserProfileSettings, that will fix the problem, but I wanted to try to fix this without it.
Current code below...
Code updating the data
var entry = profile.Settings.Where(s => s.Key == key).SingleOrDefault();
if (entry != null)
{
entry.Value = value;
} else {
var setting = /* Here create a new setting */
profile.Settings.Add(setting);
}
DataContext.SaveChanges();
Entities:
public partial class UserProfile
{
[Key]
public string UserId { get; set; }
public DateTimeOffset LastLogin { get; set; }
public ICollection<UserProfileSetting> Settings { get; set; }
}
public class UserProfileSetting
{
public UserProfileSetting() { }
public string Key { get; set; }
public string Value { get; set; }
}
Entity configuration:
public class UserProfileConfiguration : EntityTypeConfiguration<UserProfile>
{
public UserProfileConfiguration()
{
ToTable("T_USERPROFILE");
HasKey<string>(p => p.UserId);
Property(p => p.UserId)
.HasColumnName("USER_ID")
.HasMaxLength(50)
.IsUnicode()
.IsRequired();
Property(p => p.LastLogin)
.HasColumnName("LAST_LOGIN_AT")
.IsRequired();
HasMany<UserProfileSetting>(p => p.Settings)
.WithOptional()
.Map(m => m.MapKey("USER_ID"));
}
}
public class UserProfileSettingConfiguration : EntityTypeConfiguration<UserProfileSetting>
{
public UserProfileSettingConfiguration()
{
ToTable("T_USERPROFILE_SETTING");
HasKey(p => p.Key );
Property(p => p.Key)
.HasColumnName("KEY")
.HasMaxLength(50)
.IsUnicode()
.IsRequired();
Property(p => p.Value)
.HasColumnName("VALUE")
.IsUnicode()
.IsRequired();
}
}
From EF documentation...
When foreign key columns are not included in the model, the association information is managed as an independent object. Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association. The most common way to modify an independent association is to modify the navigation properties that are generated for each entity that participates in the association.
So, I was wrong. In my code, UserProfile should include UserProfileSetting either as a FK (Just the ID) or as an independent Object.
In the 1st case a UserId should be mapped into UserProfileSetting and the navigation property in UserProfile should be changed to...
HasMany<UserProfileSetting>(p => p.Settings)
.WithOptional()
.HasForeignKey(s => s.UserId);
In the 2nd case, (this is what is called an Independent Association) a new navigation property should be added into UserProfileSetting for UserProfile.
Entity framework maps to relational database and so it must stick with some of it concepts. The main thing here is, that each entity is mapped to a table containing all the records of that entity and it needs some data to distinguish the relation.
Therefore you need to add USER_ID to tell which record is for which user (to define the relation). In other words you need to have it in table and also in C# entity.
I don’t think it is possible in code first to not have the relation property on entity. On the other hand, you can create some extra DTO layer to hide it.
I have two classes as follows.
class Donkey
{
public Guid Id { get; set; }
}
class Monkey
{
public Guid Id { get; set; }
public Donkey Donkey { get; set; }
}
Now I want to configure the schema so that the relation is set in the database. Using Fluent API, I'll go something like this.
protected override void OnModelCreating(DbModelBuilder model)
{
base.OnModelCreating(model);
model.HasDefaultSchema("dbo");
...
model.Entity<Monkey>
.HasRequired(_ => _.Donkey)
.WithMany(_ => _.Monkeys)
.Map(_ => _.MapKey("DonkeyId"));
}
The problem is that now I have to declare a list of monkeys in the donkey. And I don't want to do that. I still want the monkey to point to a donkey using foreign key, so only required status won't do, because I need to specify my custom column name to store the FK pointing to the PK in the table of donkeys.
model.Entity<Monkey>.HasRequired(_ => _.Donkey);
So, the above lacks the mapping (and it doesn't compile when I just add it). Is there a way to work around it without actually changing the definition of Donkey class?
modelBuilder.Entity<Monkey>()
.HasRequired(x => x.Donkey)
.WithMany();
We have a enum Supplier
But now we need to also have some Domain data on that relation
So in 99.9% in the domain code we doe operations on the enum like product.Supplier == Suppliers.FedEx
But now we also have added product.SupplierInfo.CanAdjustPickupTime where SupplierInfo is a Entity and not just a simple enum type.
I have tried these configs
Property(p => p.Supplier)
.IsRequired()
.HasColumnName("SupplierId");
HasRequired(p => p.SupplierInfo)
.WithMany()
.HasForeignKey(p => p.Supplier); //I have also tried casting to int doing .HasForeignKey(p => (int)p.Supplier)
This will fail with
The ResultType of the specified expression is not compatible with the
required type. The expression ResultType is
'MyApp.Model.Suppliers' but the required type is
'Edm.Int32'. Parameter name: keyValues[0]
Also tried
Property(l => l.Supplier)
.IsRequired()
.HasColumnName("SupplierId");
HasRequired(p => p.SupplierInfo)
.WithMany()
.Map(m => m.MapKey("SupplierId"));
This will offcourse give the good old
One or more validation errors were detected during model generation:
SupplierId: Name: Each property name in a type must be unique.
Property name 'SupplierId' is already defined.
I could offcourse define SupplierId as a Property use that with HasForeignKey But then I need to change to .SuppliedId == (int)Suppliers.FedEx etc. Not really a solution.
I could also add a property enum that uses the SupplierId property as backing field, but this will not work with Expressions since it needs to use real mapped DB properties
Any ideas?
I have classes:
public class Agreement
{
public int Id { get; set; }
public AgreementStateTypeEnum AgreementStateId { get; set; }
}
public class AgreementState
{
public int Id { get; set; }
public string Title { get; set; }
}
context:
public class AgreementContext :DbContext
{
public AgreementContext() : base("SqlConnection") { }
public DbSet<Agreement> Agreements { get; set; }
}
In method OnModelCreating I wrote nothing.
My enum:
public enum AgreementStateTypeEnum : int
{
InReviewing = 1,
Confirmed = 2,
Rejected = 3
}
In database: in table Agreements I have foreign key AgreementStateId - it is link to table AgreementStates.
Everything is working. For example:
var temp = context.Agreements.First(x => x.AgreementStateId == AgreementStateTypeEnum.Confirmed);
I use enum how foreign key.
Finally I found the problem. (I'm using EF6, NET 4.5)
So, if you create a type Enum in your code, you couldn't create a relationship with other property virtual.
//This is wrong, when do you create a foreignkey using a type enum
//Do You should remove that's code on in your class Map.
HasRequired(p => p.SupplierInfo)
.WithMany()
.HasForeignKey(p => p.Supplier); //I have also tried casting to int doing
.HasForeignKey(p => (int)p.Supplier)
If did you created a type enum it means that you don't need for a table return data throught for a join in EF.
So, the correct code it is:
public class MyClass{
public enum myEnumType {
FedEx,
Olther
}
public int id {get;set;}
public myEnumType Supplier {get;set;}
}
//My class Map (using Fluent...)
public class MyClassMap {
HasKey(t => t.Id);
Property(t => t.Id).HasColumnName("Id");
//The type [supplier] should be [int] in database.
Property(t => t.Supplier).HasColumnName("supplier");
//That's all, you don't need write relationship, int this case
//Because, when the data returns, the EF will to do the conversion for you.
}
I hope that's useful
The best way I have found to deal with this scenario is to map Supplier as a regular domain object and create a separate class of known supplier IDs.
public class KnownSupplierIds
{
public const int FedEx = 1;
public const int UPS = 2;
// etc.
}
if (product.Supplier.SupplierId == KnownSupplierIds.Fedex) { ... };
When your code needs to check the supplier, it can compare the IDs; when you need additional info from the domain model you just load the Supplier. The reason I prefer using a class of constants instead of an enum is that the pattern works for string comparisons also and there's no need to cast.
Assume I have the following entity classes:
public class Customer {
public int Id { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public class Order {
public int Id { get; set; }
public virtual Customer Customer { get; set; }
}
How should those be mapped in Entity Framework 6 fluent code-first mapping? I want to be explicit about the mapping and not rely on automatic mapping conventions.
Option 1
Just map the local properties of both classes. That's how I would do it in Fluent NHibernate.
public class CustomerMap : EntityTypeConfiguration<Customer> {
public CustomerMap() {
HasMany(x => x.Orders);
}
}
public class OrderMap : EntityTypeConfiguration<Order> {
public OrderMap() {
HasRequired(x => x.Customer);
}
}
Option 2
Map both sides of the relationship in both classes.
public class CustomerMap : EntityTypeConfiguration<Customer> {
public CustomerMap() {
HasMany(x => x.Orders).WithRequired(x => x.Customer);
}
}
public class OrderMap : EntityTypeConfiguration<Order> {
public OrderMap() {
HasRequired(x => x.Customer).WithMany(x => x.Orders);
}
}
Option 3
Map both sides of the relation, but only in one of the classes. The code would be similar to option 2, just one of the two constructors would be empty.
Is there any difference between those options? If yes, please also explain why I should or shouldn't use a specific option.
I would go for option 3.
In option 1 you can forget to map the inverse end of an association. In this simple example it's clear that Order.Customer and Customer.Orders are two ends of the same association. When things get more complex, this isn't always obvious. Also, it is redundant code.
In option 2 you could have conflicting mappings. For instance when you have...
HasOptional(x => x.Customer).WithMany(x => x.Orders);
...in OrderMap, you will get a runtime exception telling you that both mappings don't match. And again, it is redundant code.
So option 3 is DRY and safe. The only issue is that it's a bit arbitrary where to configure the mappings. I tend to adhere to mapping children in their parent's mapping.
One more comment. You may want to add a primitive property CustomerId in Order. The mapping would look like:
public class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasMany(x => x.Orders).WithRequired(x => x.Customer)
.HasForeignKey(o => o.CustomerId);
}
}
Now you have full control over both ends of the association and the foreign key name to be used. Besides that, there are some advantages of these foreign key associations as opposed to independent associations (without a primitive foreign key property). For instance, the ability to establish an association without having to fetch the parent object from the database. You can just by set an Id value.
Using Entity Framework (Code First), I'm trying to map a conditional/filtered relationship between the following 2 entities:
Building
BuildingId
BuildingName
Area
AreaId
ParentId
AreaName
IsSubArea
A Building can have many Areas
An Area can have many (Sub)Areas
I would like to create the relationship between Building and Area where the areas marked with 'IsSubArea' are filtered out of the relationship. In this context, ParentId would relate to a Building, otherwise, ParentId would be another Area. This would allow me to create a building with many areas, and each area could have many sub-areas, creating a tree style structure.
The closest to a solution I have found relates to 'soft delete' functionality (source):
modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
Converted to fit my example:
modelBuilder.Entity<Area>().Map(m => m.Requires("IsSubArea").HasValue(false));
But as far as I can tell, this has no bearing on the relationship to the Building.
Another solution would be to create a property on the Building which specifies the query definition to use to return related areas (source):
public class Building
{
public int BuildingId {get; set;}
public string BuildingName {get; set;}
public IQueryable<Area> BuildingAreas
{
get
{
return from area in areas
where area.IsSubArea == false
and area.ParentId == BuildingId
select area;
//Assume I have a reference to relevant DbSets
}
}
}
This solution would work but doesn't feel as elegant as a conditional mapping.
Another solution would be to inherit from Area and create the 2 sub-classes:
BuildingArea
AreaId
BuildingId
AreaName
SubArea
AreaId
ParentAreaId
AreaName
Each inherits from Area and sets the 'IsSubArea' field as appropriate.
This solution feels tidier but I do not know how to implement this in Entity Framework.
Is there a way to specify conditional mapping on relationships?
Is there a better way to implement this structure?
Update 1:Found this & this guide on inheritance which seems to match my requirements. However, neither of these tutorials define relationships between derived types. I'll update the question tonight with what I have tried with regards to the Table per Hierarchy (TPH) method.
Update 2:
I'm going to try an describe the Table per Hierarchy (TPH) method I tried to implement based on the tutorial links above. Forgive me if this gets a little complicated (maybe I'm over thinking it).
Models
The building class remains the same as the OP.
I have created an abstract base class defining the Area properties common to each derived type (BuildingArea and SubArea):
public abstract class Area
{
protected Area(bool isSubArea)
{
IsSubArea = isSubArea;
SubAreas = new List<SubArea>();
}
public int AreaId { get; set; }
public int ParentId { get; set; }
public string AreaName { get; set; }
public bool IsSubArea { get; private set; } //note the private set
public virtual ICollection<SubArea> SubAreas { get; set; }
}
I then have 2 derived types which inherit from Area:
public class BuildingArea : Area
{
public BuildingArea () : base(false)
{}
public virtual Building ParentBuilding { get; set; }
}
public class SubArea : Area
{
public SubArea(): base(true)
{}
// This is of type `Area` because parent could be either `BuildingArea` or `SubArea`
public virtual Area Parent { get; set; }
}
I then have the following 2 EntityTypeConfigurations:
public class BuildingAreaMap : EntityTypeConfiguration<BuildingArea>
{
public BuildingAreaMap ()
{
// Primary Key
HasKey(t => t.AreaId);
// Properties
Property(t => t.AreaName)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
ToTable("Areas");
Property(t => t.AreaId).HasColumnName("AreaId");
Property(t => t.ParentId).HasColumnName("ParentId");
Property(t => t.AreaName).HasColumnName("AreaName");
Property(t => t.IsSubArea).HasColumnName("IsSubArea");
// This is the discriminator column
Map(m => m.Requires("IsSubArea").HasValue(false));
HasRequired(a => a.Site).WithMany(s => s.SiteAreas).HasForeignKey(k => k.ParentId);
}
public class SubAreaMap : EntityTypeConfiguration<SubArea>
{
public SubAreaMap()
{
// Primary Key
HasKey(t => t.AreaId);
// Properties
Property(t => t.AreaName)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
ToTable("AssetHealthAreas");
Property(t => t.AreaId).HasColumnName("AreaId");
Property(t => t.ParentId).HasColumnName("ParentId");
Property(t => t.AreaName).HasColumnName("AreaName");
Property(t => t.IsSubArea).HasColumnName("IsSubArea");
// This is the discriminator column
Map(m => m.Requires("IsSubArea").HasValue(true));
HasRequired(a => a.Parent).WithMany(s => s.SubAreas).HasForeignKey(k => k.ParentId);
}
}
This code builds successfully, but I do get the following runtime error:
Map was called more than once for type 'SiteArea' and at least one of the calls didn't specify the target table name.
But I am specifying the target table name (once in each EntityTypeConfiguration class).
So I removed the EntityTypeConfiguration for SubArea but I get the same error.
One of the tutorials pulls the mapping out of the EntityTypeConfiguration class and puts it in the OnModelCreating handler as follows:
modelBuilder.Entity<Area>()
.Map<BuildingArea>(m => m.Requires("IsSubArea").HasValue(false))
.Map<SubArea>(m => m.Requires("IsSubArea").HasValue(true));
This also gives me the same error.
If I remove relationships from the equation, I get a different error regarding the ParentId property:
The foreign key component 'ParentId' is not a declared property on type 'SiteArea'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
Update 3
An image of the model I'm trying to create...
Update 4
I'm going to try and simplify my model to match the following.
If the solution below works, I will need to have a little more business logic to navigate the tree but it should be manageable.
For the errors when creating the TPH classes:
I think this is because you are not supposed to have the discriminator column as a property in your classes.
Remove the property IsSubArea from your base class.
When you create new instance, EF should automatically detect the type and fill the IsSubArea accordingly.