EF - class split into different tables - entity-framework

I have an existing software that has tree tables for a single class.
users, users_a and users_b with the same primary key user_id.
How can I model such kind of tables with Entity Framework and C# - Code First, so I have one class User and all the properties from the three tables are assigned to this class?
table: users
users_id int
name nvarchar
....
table: users_a
users_id int
race_id int
......
table: users_b
users_id int
genders_id int
...
I need a class User
User
public int Id { get;set;}
public int GenderId {get;set;}
public virtual Gender Gender {get;set;}
public int RaceId {get;set;}
public virtual Race Race {get;set;}

Split the class by overriding the "OnModelCreating" method of the DbContext Class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Map(map =>
{
map.Properties(p => new
{
p.UserId,
p.Name
});
map.ToTable("users");
})
.Map(map =>
{
map.Properties(p => new
{
p.UserId,
p.GenderId
});
map.ToTable("users_b");
})
.Map(map =>
{
map.Properties(p => new
{
p.UserId,
p.RaceId
});
map.ToTable("users_a");
});
}

This structure can be implemented by using Table per Hierarchy. For details see Asp.net example

Related

The column name is specified more than once inserting into entities with 1 to 1 relationship

I have two tables that join in a 1 to 1 relationship
using System.ComponentModel;
public class Product
{
public Product()
{
MaterialProperties = new MoreProductInfo
{
Product = this
};
}
[Key]
public int ItemId { get; set; }
[ForeignKey("ItemId")]
public virtual MoreProductInfo MaterialProperties { get; set; }
}
public class MoreProductInfo : IObjectSpaceLink, IElipseLookUp
{
[Key]
public int ItemID { get; set; }
[ForeignKey("ItemId")]
public virtual Product Product { get; set; }
}
The relationship is set up in FluentAPI like this:
modelBuilder.Entity<Product>()
.HasOne<MoreProductInfo>(x => x.MaterialProperties)
.WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ItemID);
When I try to save a new product I get
Microsoft.Data.SqlClient.SqlException
HResult=0x80131904
The column name 'ItemID' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
Source=Core Microsoft SqlClient Data Provider
StackTrace:
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
I am using XAF 21.2.8 on .NET 6 and Entity Framework Core 5.0.14
I tried the following;
modelBuilder.Entity<Product>()
.HasOne(x => x.MaterialProperties)
.WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ItemID)
.HasForeignKey<Product>(x => x.ItemId)
.HasPrincipalKey<Product>(x => x.ItemId)
.HasPrincipalKey<MoreProductInfo>(x = >x.ItemID);
But this gives the error
System.InvalidOperationException
HResult=0x80131509
Message=The principal and dependent ends of the relationship cannot be inverted once foreign key or principal key properties have been specified.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Metadata.Builders.InvertibleRelationshipBuilderBase..ctor(InternalForeignKeyBuilder builder, InvertibleRelationshipBuilderBase oldBuilder, Boolean inverted, Boolean foreignKeySet, Boolean principalKeySet, Boolean requiredSet)
I got it working by adjusting the attributes to use a different column name in one of the entities as follows
public class MoreProductInfo : IObjectSpaceLink, IElipseLookUp
{
[Column("ItemID")]
[Key] public int ExtItemID { get; set; }
[ForeignKey("ExtItemID")]
public virtual Product Product { get; set; }
// etc
and correcting the Fluent api
modelBuilder.Entity<Product>().HasOne(x => x.MaterialProperties).WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ExtItemID).HasPrincipalKey<Product>(x => x.ItemId);

TPH Discriminator not on base class / table

I'm having an issue with discriminators in TPH inheritance with Entity Framework v6.1.1
I would expect that the discriminator should fall on the table that is representative of the base class. However, it appears EF is trying to map the discriminator column to the table mapped to the derived class
E.g.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Staff : Person
{
public decimal? Salary { get; set; }
}
public class MyContext : DbContext
{
public MyContext()
: base("MyConnectionString") {}
public virtual IDbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.Map(x => x.ToTable("Person"))
.Map<Staff>(x => x.ToTable("Staff").Requires("PersonTypeId").HasValue(1));
}
}
I am also using an existing schema too - i.e:
CREATE TABLE Person
(
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Name VARCHAR(50) NULL,
PersonTypeId INT NOT NULL
)
CREATE TABLE Staff
(
Id INT NOT NULL REFERENCES Person(Id),
Salary DECIMAL NULL
)
GO
However, when I try to add a new Staff, I encounter the following exception:
'System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'PersonTypeId', table 'MyDb.dbo.Person'; column does not allow nulls. INSERT fails.
The statement has been terminated'
It appears that it is trying to insert the discriminator (incorrectly) to the derived table. Hope someone can help.
So it appears that I've mis-understood the TPH setup for Entity Framework.
In my case, I'm wanting to map derived types to separate tables, which is an example of Table per Type - http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt
Discriminators are redundant in this context.

Code First: TPT inheritance - Specify a different name for the primary key column in each table

I've posted my problem on codeplex http://entityframework.codeplex.com/workitem/2087.
There are also some questions posted here but they are not successfully answered.
See
Mapping TPT in EF Code First 4.1 w/ Different Primary Keys
Entity Framework 4 - TPT Inheritance in Features CTP5 (code first): rename foreign key column on inherited table
How can I use TPT inheritance models when primary keys have different names?
Is it now possible to have different column names for the primary keys when using TPT?
May be with 6.1.0
In TPT you're essentially do not want to declare the key in the subclasses, you'd miss the point otherwise.
If you must have a different Id name, just make proxy properties in the subclasses mapping to the base Id one.
public class BaseEntity
{
public int Id { get; set; }
}
public abstract class SubEntity : BaseEntity
{
public BaseId
{
get => Id;
set => Id = value;
}
}
Consider marking the sub fields as NotMapped, which in case you shouldn't include them in your LINQ queries.
With EF 6.4 I was able to use the ColumnAttribute to rename the Primary Key column in the dependent class
[Table("Person")]
public class Person
{
[Key]
public virtual int PersonId { get; set; }
// Person atributes...
}
[Table("Employee")]
public class Employee : Person
{
[Column("EmployeeId")] // <- Name of the primary Key column in the Database
public override int PersonId { get; set }
// Employee Attributes
}
Look at this code snip. Its work correct for me:
public partial class Person
{
// Any other PK name can thrown an exception
public int ID { get; set; }
}
public partial class Employee : Person
{
// Hide base class ID
private new int ID { get; set }
// Define derived class ID (that wrapped inherited ID)
[NotMapped]
public int EmployeeID
{
get { return base.PersonID; }
set { base.PersonID = value; }
}
}
Now, we must rename the inherited ID (with fluent API) for database table:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Property(e => e.ID)
.HasColumnName("EmployeeID");
}

Change name of Identity Column for all Entities

I am in the process of creating a domain model and would like to have a "BaseEntity" class with an "Id" property (and some other audit tracking stuff). The Id property is the primary key and each Entity in my Domain Model will inherit from the BaseEntity class. Pretty straightforward stuff.....
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime LastUpdate { get; set; }
public string LastUpdateBy { get; set; }
}
public class Location : BaseEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
Using the example above, I would like to map the "Id" field to a "LocationId" column. I understand that I can use the modelBuilder to do this for each entity explicitly by doing something like this:
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
But I would like to do this for every Entity in my domain model and it would be ugly.
I tried the following bit of reflection but did not have any luck. For whatever reason, the compiler "cannot resolve symbol type":
foreach (var type in GetTypesInNamespace(Assembly.Load("Domain.Model"),"Domain.Model"))
{
modelBuilder.Entity<type>().Property(x=>x.Id).....
}
Is there a way to define a convention to override the default PrimaryKey convention to map my "Id" property to a "ClassNameId" property in the database? I am using Entity Framework 6.
You should take a look at Custom Code First Conventions. You need EF6 for it to work, but it looks like you're already using it.
Just to give you an overview, take a look at the following convention I've used to convert PascalCase names to underscore names. It includes a convention for id properties... It also includes an optional table name prefix.
public class UnderscoreNamingConvention : IConfigurationConvention<PropertyInfo, PrimitivePropertyConfiguration>,
IConfigurationConvention<Type, ModelConfiguration>
{
public UnderscoreNamingConvention()
{
IdFieldName = "Id";
}
public string TableNamePrefix { get; set; }
public string IdFieldName { get; set; }
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
var columnName = propertyInfo.Name;
if (propertyInfo.Name == IdFieldName)
columnName = propertyInfo.ReflectedType.Name + IdFieldName;
configuration().ColumnName = ToUnderscore(columnName);
}
public void Apply(Type type, Func<ModelConfiguration> configuration)
{
var entityTypeConfiguration = configuration().Entity(type);
if (entityTypeConfiguration.IsTableNameConfigured) return;
var tableName = ToUnderscore(type.Name);
if (!string.IsNullOrEmpty(TableNamePrefix))
{
tableName = string.Format("{0}_{1}", TableNamePrefix, tableName);
}
entityTypeConfiguration.ToTable(tableName);
}
public static string ToUnderscore(string value)
{
return Regex.Replace(value, "(\\B[A-Z])", "_$1").ToLowerInvariant();
}
}
You use it like this
modelBuilder.Conventions.Add(new UnderscoreNamingConvention { TableNamePrefix = "app" });
EDIT: In your case, the Apply method should be something like this:
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
if (propertyInfo.Name == "Id")
{
configuration().ColumnName = propertyInfo.ReflectedType.Name + "Id";
}
}
Try this out in your DbContext class;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name.Equals("Id"))
.Configure(c => c.HasColumnName(c.ClrPropertyInfo.ReflectedType.Name + "Id"));
}
int is the CLR Type of my Primary Key fields. I want to refer to all keys in code as Id but DBA's require keys to be Id with Table entity name prefix. Above gives me exactly what I want in my created database.
Entity Framework 6.x is required.
In Entity Framework 6 Code First:
modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
and update-database...
Change in model
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
to:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long role_id { get; set; }
Then remove this:
//modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
A start to the Dynamic approach if NOT using custom conventions
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
You can do this using reflection on the context. Pseudo Code as explanation:
Reflect Context to get a list of POCO names
For each POCO in a dbcontext.
Map Property Id -> string PocoName+Id
Here are the extensions I use for this type of solution.
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetModelTypes(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetTypes(propList);
}
// DBSet Types POCO types as IEnumerable List
public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
.Name.StartsWith("DbSet"))
.Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
}
private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.PropertyType.GenericTypeArguments[0].Name)
.ToList();
return modelTypeNames;
}
private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.Name)
.ToList();
return modelNames;
}
However, you will still need to employee dynamic lambda to finish.
Continue that topic here: Dynamic lambda example with EF scenario
EDIT:
Add link to another question that address the common BAse Config class approach
Abstract domain model base class when using EntityTypeConfiguration<T>
Piggybacking on #Monty0018 's answer but this just need to be updated a little if, like me, you're using Entity Framework 7 and/or SQLite.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
try
{
_builder = modelBuilder;
var typeName = typeof(T).Name;
_builder
.Entity(typeof(T))
.Property<int>("Id")
.ForSqliteHasColumnName(typeName + "Id");
}
catch (Exception e)
{
throw e;
}
}

Relationship between POCO classes in Entity Framework, code first

I reimplementing database created automatically by SimpleMembershipProvider. Actually I have a question about 2 tables linking:
create table user_profiles
(
id int not null identity, /* PK */
username varchar(128) not null,
.........
);
create table membership
(
userid int not null, /* FK to user_profile. */
..............
);
I'd like to create relationship between initial POCO classes:
public class UserProfile : BaseType
{
public virtual Membership Membership { get; set; }
......
public string UserName { get; set; }
......
}
public class Membership
{
public virtual int UserId { get; set; }
public virtual UserProfile User { get; set; }
......
}
In Membership property UserId used as PK and in the same time as FK in database. I tried following configurations:
public class UserProfileConfiguration : EntityTypeConfiguration<UserProfile> {
public UserProfileConfiguration() {
HasKey(k => k.Id);
Map(m => m.ToTable("user_profiles"));
HasRequired(t => t.Membership)
.WithRequiredPrincipal(t1 => t1.User)
.Map(m => m.MapKey("userid"));
....
}
}
public class MembershipConfiguration : EntityTypeConfiguration<Membership> {
public MembershipConfiguration() {
HasKey(k => k.UserId);
Map(m => m.ToTable("webpages_Membership"));
//Property(x => x.UserId).HasColumnName("userid");
}
}
When line in MembershipConfiguration commented out (like in sample) command Add-Migration creates 2 records in migration command:
c => new {
UserId = c.Int(nullable: false, identity: true),
.............
userid = c.Int(nullable: false),
If I uncommenting it command failed with error message Each property name in a type must be unique. Property name 'userid' was already defined.
How could I claim required result, use column 'userid' as PK and FK in the same time?