EF Core 5 adds shadow alternate key to some entities but does not use the property - entity-framework

UPDATED: The sample code listed below is now complete and sufficient
to generate the shadow alternate key in Conference. When the Meeting
entity inherits from a base entity containing a RowVersion attribute
the shadow alternate key is generated in the Conference entity.
If that attribute is included directly in the Meeting entity,
without inheritance, the shadow alternate key is not generated.
My model worked as expected in EF Core 3.1. I upgraded to .Net 5 and EF Core 5, and EF adds shadow alternate key attribute(s) named TempId to several entities. EF can't load those entities unless I add those attributes to the database. The shadow alternate key properties are NOT used in any relationships that I can find in the model. Virtually all discussion of shadow properties is either for foreign keys or hidden attributes. I can't find any explanation for why EF would add a shadow alternate key, especially if it doesn't use the attribute. Any suggestions?
One of the entities that gets a shadow alternate key is Conference, which is the child in one relationship and the parent in another. I have many similar entities which do NOT get a shadow alternate key, and I cannot see any difference between them.
I loop through the model entities identifying all shadow properties and all relationships using an alternate key for the principal key. None of the shadow alternate keys are used in a relationship. I do see the two defined relationships where I specifically use an alternate key, so I believe my code is correct.
Here is a complete simplified EF context and its two entities which demonstrates the problem.
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFShadow
{
public partial class Conference
{
public Conference()
{
Meetings = new HashSet<Meeting>();
}
[Key]
public string ConferenceCode { get; set; }
[Required]
public string ConferenceName { get; set; }
public ICollection<Meeting> Meetings { get; }
}
public partial class Meeting : BaseEntity
{
public Meeting() { }
[Key]
public int MeetingId { get; set; }
[Required]
public string ConferenceCode { get; set; }
[Required]
public string Title { get; set; }
public Conference Conference { get; set; }
}
[NotMapped]
public abstract partial class BaseEntity
{
[Timestamp]
public byte[] RowVersion { get; set; }
}
public class EFShadowContext : DbContext
{
public EFShadowContext(DbContextOptions<EFShadowContext> options)
: base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
}
public DbSet<Conference> Conferences { get; set; }
public DbSet<Meeting> Meetings { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Conference>(entity =>
{
entity.HasKey(e => e.ConferenceCode);
entity.ToTable("Conferences", "Settings");
entity.Property(e => e.ConferenceCode)
.IsRequired()
.HasMaxLength(25)
.IsUnicode(false)
.ValueGeneratedNever();
entity.Property(e => e.ConferenceName)
.IsRequired()
.HasMaxLength(100);
});
builder.Entity<Meeting>(entity =>
{
entity.HasKey(e => e.MeetingId);
entity.ToTable("Meetings", "Offerings");
entity.Property(e => e.ConferenceCode).HasMaxLength(25).IsUnicode(false).IsRequired();
entity.Property(e => e.Title).HasMaxLength(255).IsRequired();
//Inherited properties from BaseEntityWithUpdatedAndRowVersion
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
entity.HasOne(p => p.Conference)
.WithMany(d => d.Meetings)
.HasForeignKey(d => d.ConferenceCode)
.HasPrincipalKey(p => p.ConferenceCode)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("Meetings_FK_IsAnOccurrenceOf_Conference");
});
}
}
}
Here is the code I use to identify the shadow key.
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConferenceEF.Code
{
public class EFModelAnalysis
{
readonly DbContext _context;
public EFModelAnalysis(DbContext context)
{
Contract.Requires(context != null);
_context = context;
}
public List<string> ShadowProperties()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
var entityProperties = entityType.GetProperties();
foreach (var entityProperty in entityProperties)
{
if (entityProperty.IsShadowProperty())
{
string output = $"{entityType.Name}.{entityProperty.Name}: {entityProperty}.";
results.Add(output);
}
}
}
return results;
}
public List<string> AlternateKeyRelationships()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
foreach (var fk in entityType.GetForeignKeys())
{
if (!fk.PrincipalKey.IsPrimaryKey())
{
string output = $"{entityType.DisplayName()} Foreign Key {fk.GetConstraintName()} " +
$"references principal ALTERNATE key {fk.PrincipalKey} " +
$"in table {fk.PrincipalEntityType}.";
results.Add(output);
}
}
}
return results;
}
}
}
Here is the context initialization and processing code.
var connectionSettings = ((LoadDataConferencesSqlServer)this).SqlConnectionSettings;
DbContextOptionsBuilder builderShadow = new DbContextOptionsBuilder<EFShadowContext>()
.UseSqlServer(connectionSettings.ConnectionString);
var optionsShadow = (DbContextOptions<EFShadowContext>)builderShadow.Options;
using EFShadowContext contextShadow = new EFShadowContext(optionsShadow);
EFModelAnalysis efModelShadow = new EFModelAnalysis(contextShadow);
var shadowPropertiesShadow = efModelShadow.ShadowProperties();
foreach (var shadow in shadowPropertiesShadow)
progressReport?.Report(shadow); //List the shadow properties
var alternateKeysShadow = efModelShadow.AlternateKeyRelationships();
foreach (var ak in alternateKeysShadow)
progressReport?.Report(ak); //List relationships using alternate key
The output I get is:
EFShadow.Conference.TempId: Property: Conference.TempId (no field, int) Shadow Required AlternateKey AfterSave:Throw.
No relationship uses this alternate key.
If I eliminate the Meeting entity's inheritance from BaseEntity and include the RowVersion timestamp property directly in Meeting, no shadow key is generated. That's the only change required to make the difference.

Tricky confusing issue, worth reporting it to EF Core GitHub issue tracker.
Using trial and error approach, looks like the strange behavior is caused by the [NotMapped] data annotation applied to the base class.
Remove it from there (and all other similar places) and the problem is solved. In general don't apply that attribute on model classes. Normally you don't need to explicitly mark a class as "non entity" if its is not referenced by navigation property, DbSet or Entity<>() fluent call. And if you really want to make sure explicitly it isn't used as entity, use Ignore fluent API instead, because the attribute breaks the default conventions which are applied before OnModelCreating.
e.g.
//[NotMapped] <-- remove
public abstract partial class BaseEntity
{
[Timestamp]
public byte[] RowVersion { get; set; }
}
and optionally
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Ignore<BaseEntity>(); // <-- add this
// the rest...
}

Related

Entity Framework Core 5.0 - Many to many select query

I am trying to get a single User, with a list of Items, mapped with a many-to-many entity UserItems. However, I am unable to retrieve the mapped Items due to to an error that I'm unable to solve (error at bottom of question). Here is my code:
public class User
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class UserItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
public int Quantity { get; set; }
}
The UserItem class configuration has the following relationships defined:
builder.HasOne(x => x.User)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(x => x.Item)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.ItemId)
.OnDelete(DeleteBehavior.ClientCascade);
I have the following generic repo with this method:
public class GenericRepository<T> : where T : class
{
private readonly DbContext _context;
public GenericRepository(DbContext context) => _context = context;
public T Get(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] navigationProperties)
{
IQueryable<T> query = _context.Set<T>();
query = navigationProperties.Aggregate(query, (current, property) => current.Include(property));
var entity = query.FirstOrDefault(where);
return entity;
}
}
However, when I try to run the code, I get an error on the Select(x => x.Item):
var user = repo.Get(x => x.Id == 1, x => x.UserItems.Select(y => y.Item));
Error:
System.InvalidOperationException: 'The expression 'x.UserItems.AsQueryable().Select(y => y.Item)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
What am I doing wrong, this seems to work for my other projects?
This error Occurs because you are not passing in a navigation property (x.UserItems would be a navigation property) but rather something you want to do with the navigation property. UserItems.Select(y => y.Item) is not a property of x because Select() is a function and therefore it cannot be included.
What you are trying to do (I assume it is including UserItems and also the corresponding Items) is not going to work with your current implementation of the repository. To include navigation properties of navigation properties .ThenInclude() must be used instead of .Include() which works only for navigation properties directly defined on the Entity the DbSet is created for.
But apart from your question I would suggest not to use such an generic implementation of Repository. The main benefit from using reposiories is to separarte code related to loading and storing of entities from the rest of your code. In your case if the consumer of repository knows that navigation properties must be included and that he has to provide them - then what is the point of having a repository at all? Then the consumer again cares about database specific code which makes having a repository unneccessary. I would recommend just making a conrete "UserRepository" which can only be used to retrieve users and explicitly includes the needed properties.

InvalidOperationException: The entity type 'Enrollments' requires a primary key to be defined

I am new to Asp.Net Core (Even to Asp.Net and web). I am using Asp.Net Core 2 with MySQL, using Pomelo.EntityFrameWorkCore.MySql (2.0.1) driver. I just created a custom dbcontext with Courses and Enrollments table, along with the default created ApplicationDbContext. The Primary Key for Enrollments is a composite key, comprising of UserId and CourseId. Below is the code :
public class CustomDbContext : DbContext
{
public virtual DbSet<Courses> Courses { get; set; }
public virtual DbSet<Enrollments> Enrollments { get; set; }
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Courses>(entity =>
{
entity.ToTable("courses");
entity.HasIndex(e => e.Name)
.HasName("Coursescol_UNIQUE")
.IsUnique();
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Duration).HasColumnType("time");
entity.Property(e => e.Name).HasMaxLength(45);
});
modelBuilder.Entity<Enrollments>(entity =>
{
entity.HasKey(e => new { e.UserId, e.CourseId });
entity.ToTable("enrollments");
entity.HasIndex(e => e.CourseId)
.HasName("fk_Courses_Enrollments_CourseId_idx");
entity.HasIndex(e => e.UserId)
.HasName("fk_Users_Enrollments_CourseId_idx");
entity.HasIndex(e => new { e.UserId, e.CourseId })
.HasName("UniqueEnrollment")
.IsUnique();
entity.Property(e => e.CourseId).HasColumnType("int(11)");
entity.HasOne(d => d.Course)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.CourseId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Courses_Enrollments_CourseId");
entity.HasOne(d => d.User)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Users_Enrollments_UserId");
});
}
}
The Program.cs goes like :
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<CustomDbContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
The configure services method in Startup.cs goes like :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<CustomDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
The Courses Model goes like :
public partial class Courses
{
public Courses()
{
Enrollments = new HashSet<Enrollments>();
}
public int Id { get; set; }
public string Name { get; set; }
public TimeSpan? Duration { get; set; }
public ICollection<Enrollments> Enrollments { get; set; }
}
The Enrollments Model goes like :
public partial class Enrollments
{
public string UserId { get; set; }
public int CourseId { get; set; }
public Courses Course { get; set; }
public ApplicationUser User { get; set; }
}
The applicationUser model goes like :
public ApplicationUser()
{
Enrollments = new HashSet<Enrollments>();
}
public ICollection<Enrollments> Enrollments { get; set; }
Now, here's what I've tried so far :
If i add Course and Enrollment model to the ApplicationDBContext, then everything goes fine.
If in CustomDBContext i have a non-composite primary Key, even then it works fine. (I just tried another example)
Can somebody please throw some light on why is this error ? Is this the intended way to handle such a case ?
Thanks in advance.
It's because the Enrollments entity has been discovered by ApplicationDbContext through ApplicationUser.Enrollments navigation property. This is explained in the Including & Excluding Types - Conventions section of the EF Core documentation:
By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.
I guess now you see the problem. The Enrollments is discovered and included in the ApplicationDbContext, but there is no fluent configuration for that entity there, so EF uses only the default conventions and data annotations. And of course composite PK requires fluent configuration. And even there wasn't a composite PK, it's still incorrect to ignore the existing fluent configuration. Note that Courses is also included in the ApplicationDbContext by the aforementioned recursive process (through Enrollments.Courses navigation property). Etc. for other referenced classes.
Note that the same applies in the other direction. ApplicationUser and all referenced from it are discovered and included in the CustomDbContext w/o their fluent configuration.
The conclusion - don't use separate contexts containing interrelated entities. In your case, put all the entities in the ApplicationDBContext.

Entity Framework 6.1 Unidirectional Navigation Property 0 to Many with Fluent API

I'm new to EF and am having issues trying to create a unidirectional navigation association (0 to many) using the Fluent API. Here are simplified versions of the classes:
public partial class Company
{
public int Id { get; set; }
// "Company" is NOT REQUIRED to have any BillingInfo records/objects
public virtual IList<BillingInfo> BillingInfos { get; set; }
}
public partial class BillingInfo
{
public int Id { get; set; }
// A "BillingInfo" requires ONE "Company"
public int Company_Id { get; set; }
}
I'm using EF 6.1 Code First with migrations enabled along with SQL Server 2012.
In my derived EntityTypeConfiguration classes for "Company" and "BillingInfo", I've tried every which way I can think of to achieve:
A Company DOES NOT REQUIRE any BillingInfo records, but MAY HAVE MANY.
A BillingInfo DOES REQUIRE only ONE Company.
Maintain a unidirectional navigation between Company and BillingInfo. (don't want to have a Company navigation property on BillingInfo)
EF Migration creates the NON-NULLABLE Company_Id field in database, WITH a defined ForeignKey constraint.
All the methods I've tried, only gets me partially there. The closest I've come is this (but the only thing missing is the foreignkey constraint isn't created):
class CompanyConfig : EntityTypeConfiguration<Company>
{
public CompanyConfig()
{
this.HasOptional(company => company.BillingInfos)
.WithMany()
.Map(m => m.MapKey("Company_Id"));
}
}
Any ideas???
I think you should use the following code:
private class CompanyMapping : EntityTypeConfiguration<Company>
{
public CompanyMapping()
{
this.HasMany(o => o.BillingInfos).WithOptional().HasForeignKey(fk => fk.Company_Id);
}
}
private class BillingInfoMapping : EntityTypeConfiguration<BillingInfo>
{
public BillingInfoMapping()
{
this.HasOptional(o => o.Company).WithMany(c=>c.BillingInfos).HasForeignKey(fk => fk.Company_Id);
}
}

How to change the naming convention of Many-to-Many table relationships?

How can I go about changing the naming convention of the auto-generated many-to-many table?
Assume I have two classes:
public class User
{
public int UserId { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public virtual List<User> Users { get; set; }
}
By Default, this will create a table called UserRoles.
I can change the name of that one table to UsersInRoles, for example, by using the following in the OnModelCreating override of my DbContext:
modelBuilder.Entity<User>()
.HasMany(p => p.Roles)
.WithMany(p => p.Users)
.Map(mc =>
{
mc.MapLeftKey("UserId");
mc.MapRightKey("RoleId");
mc.ToTable("UsersInRoles");
});
However, what I really want to do is change the naming convention so that by default, all auto-generated many-to-many tables use this new convention. I cannot figure out how to do that, or if it's even possible. I do not like having to specify 9 lines of extra code every time I specify one of these relationships.
I am currently using EF version 6.0.0-rc1.
The ability to control relationships was removed from the basic conventions API before release because it wasn't in a usable state. You can access all of the properties and tables in the model through model based conventions. An overview of model based conventions is available here: http://msdn.microsoft.com/en-US/data/dn469439
This solution involves a little more digging around in the metadata API, EntitySet is the correct type for this scenario
This convention should rename the generated relation table:
public class MyConvention : IStoreModelConvention<EntitySet>
{
public void Apply(EntitySet set, DbModel model)
{
var properties = set.ElementType.Properties;
if (properties.Count == 2)
{
var relationEnds = new List<string>();
int i = 0;
foreach (var metadataProperty in properties)
{
if (metadataProperty.Name.EndsWith("_ID"))
{
var name = metadataProperty.Name;
relationEnds.Add(name.Substring(0, name.Length - 3));
i++;
}
}
if (relationEnds.Count == 2)
{
set.Table = relationEnds.ElementAt(0) + "_" + relationEnds.ElementAt(1) + "_RelationTable";
}
}
}

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;
}
}