i know you can define the entity's schema name per class by using ToTable("TableName", "SchemaName") but is there a way to set it up so you can set the schema name for all tables in the configuration as i am getting some weird results when i am using some types of relationship mapping and split entity mapping where it is reverting back to the default dbo.TableName in the internal sql queries
see this earlier post for sql output example
Im having Oracle database-first with EF 4.1, all mappings done with Data Annotations. Different Schema names in Test and Production environments. My solution is to map the Schema dynamically during OnModelCreating with some help of fluent API, reflection and late binding. Iterate through all Context class properties of generic type and do the dirty work. Works for me so far.
public class Context : DbContext
{
public Context()
: base(new OracleConnection(ConfigurationManager.ConnectionStrings["OraAspNetConString"].ConnectionString), true)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
foreach (var p in typeof(Context).GetProperties().Where(foo=>foo.PropertyType.IsGenericType))
{
// this is what we are trying to accomplish here --
//modelBuilder.Entity<User>().ToTable("TBL_USERS", "TestSchema");
Type tParam = p.PropertyType.GetGenericArguments()[0]; // typeof User
MethodInfo generic = typeof(DbModelBuilder).GetMethod("Entity").MakeGenericMethod(new[] { tParam });
object entityTypeConfig = generic.Invoke(modelBuilder, null);
MethodInfo methodToTable = typeof(EntityTypeConfiguration<>).MakeGenericType(new[] { tParam }).GetMethod("ToTable", new Type[] { typeof(string), typeof(string) });
methodToTable.Invoke(entityTypeConfig, new[] { GetMappedTableName(tParam), currentOraSchemaName });
}
base.OnModelCreating(modelBuilder);
}
private string currentOraSchemaName = ConfigurationManager.AppSettings.Get("OraSchemaName");
private string GetMappedTableName(Type tParam)
{
TableAttribute tableAttribute = (TableAttribute)tParam.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
return tableAttribute.Name;
}
}
The user class here, with no hard-coded schema mapping --
[Table("TBL_USERS")]
public class User
{
[Column("USER_ID")]
public string UserId { get; set; }
[Column("USER_NAME")]
public string Name { get; set; }}
Since final EFv4.1 version doesn't have public API for custom conventions you cannot change the schema globally from the API.
Related
I'd like to use strongly typed Ids in my entity model, mapped to auto-incremented int in DB (e.g. MS SQL Identity column). But EF Core 3.1 doesn't support the identity on custom type:
public class Vendor
{
public VendorId Id { get; private set; }
public string CompanyName { get; set; }
}
public class VendorId
{
public int Value { get; }
public VendorId(int value)
{
Value = value;
}
}
public void Configure(EntityTypeBuilder<Vendor> vendorEntity)
{
vendorEntity.HasKey(vendor => vendor.Id);
vendorEntity.Property(vendor => vendor.Id)
.HasConversion(vendorId => vendorId.Value, dbValue => new VendorId(dbValue))
.UseIdentityColumn();
}
With this model I get exception:
Identity value generation cannot be used for the property 'Id' on entity type 'Vendor' because the property type is 'VendorId'. Identity value generation can only be used with signed integer properties.
I also tried to map to private integer backing field int id, but then I can't use the Id property in EF queries. So it doesn't looks to be a solution.
Similar problems are when I try HiLo key generation.
Do you have any ideas how to solve this problem?
the solution i've used (in ef core 5) using c# 9 records:
public abstract record StronglyTypedId<TValue>(TValue Value) where TValue : notnull
{
public override string ToString() => Value.ToString();
}
with an abstract entity model:
public abstract class Entity<TId, T> where TId: StronglyTypedId<T>
{
T _Id;
public virtual TId Id
{
get { return (TId)Activator.CreateInstance(typeof(TId), new object[] {_Id}); }
}
}
then an example entity model
using TId = Int32;
public record EntityTypeId(TId Value) : StronglyTypedId<TId>(Value);
public class EntityName<EntityTypeId, TId>
{
private EntityName() {} //for ef core
}
then in the model builder (which can again be an abstract / base model builder)
builder.Ignore(e => e.Id);
builder
.Property("_Id")
.HasColumnName("Id")
.UseIdentityColumn();
builder.HasKey("_Id");
this does mean that when retrieving from the database (via command or repository) you need to use the name e.g.
public async Task<EntityName> GetEntity(EntityTypeId typedId)
{
var entity = await context.EntityName
.FirstOrDefaultAsync(x => EF.Property<int>(x, "_Id") == typedId.Value);
return entity;
}
not perfect but works
I am trying to restrict a couple of generic methods to only be allowed Entities that inherit from the IParentOf<TChildEntity> interface, as well as accessing an Entity's Foreign Key (ParentId) Generically.
To demonstrate;
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee)
where TParentEntity : DataEntity, IParentOf<TChildEntity>
where TChildEntity : DataEntity, IChildOf<TParentEntity>
{
foreach (TChildEntity child in (IParentOf<TChildEntity>)parent.Children)
{
(IChildOf<TParentEntity)child.ParentId = adoptee.Id;
}
}
A child entity class model would look like this,
public class Account : DataEntity, IChildOf<AccountType>, IChildOf<AccountData>
{
public string Name { get; set; }
public string Balance { get; set; }
// Foreign Key and Navigation Property for AccountType
int IChildOf<AccountType>.ParentId{ get; set; }
public virtual AccountType AccountType { get; set; }
// Foreign Key and Navigation Property for AccountData
int IChildOf<AccountData>.ParentId{ get; set; }
public virtual AccountData AccountData { get; set; }
}
First of all, is this possible to do? Or will it breakdown in EF?
Secondly, since the Foreign Keys do not follow convention (and there are multiple) how do I set them via Fluent Api? I can see how to do this in Data Annotations.
I hope this is clear, I have been considering it for a while and trying to work round it, so I can follow my argument, but it may not be clearly conveyed, so please ask for clarification if needed. My reason for wanting to do this is to make the code safe as well as automating a lot of the manual changing of classes necessary to add new associations and entities.
Thanks.
Edit
I decided to create some basic classes to implement this idea and test it, my code is as follows.
public abstract class ChildEntity : DataEntity
{
public T GetParent<T>() where T : ParentEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is T entity)
return entity;
}
return null;
}
}
public abstract class ParentEntity : DataEntity
{
public ICollection<T> GetChildren<T>() where T : ChildEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is ICollection<T> collection)
return collection;
}
return null;
}
}
public interface IParent<TEntity> where TEntity : ChildEntity
{
ICollection<T> GetChildren<T>() where T : ChildEntity;
}
public interface IChild<TEntity> where TEntity : ParentEntity
{
int ForeignKey { get; set; }
T GetParent<T>() where T : ParentEntity;
}
public class ParentOne : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Amount { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ParentTwo : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Value { get; set; }
public virtual ICollection<ChildOne> ChildOnes { get; set; }
}
public class ChildOne : ChildEntity, IChild<ParentOne>, IChild<ParentTwo>
{
public string Name { get; set; }
public decimal Balance { get; set; }
int IChild<ParentOne>.ForeignKey { get; set; }
public virtual ParentOne ParentOne { get; set; }
int IChild<ParentTwo>.ForeignKey { get; set; }
public virtual ParentTwo ParentTwo { get; set; }
}
Data Entity simply gives each entity an Id property.
I have standard Generic Repositories set up with a Unit of Work class for mediating. The AdoptAll method looks like this in my program.
public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee, UoW uoW)
where TParentEntity : DataEntity, IParent<TChildEntity>
where TChildEntity : DataEntity, IChild<TParentEntity>
{
var currentParent = uoW.GetRepository<TParentEntity>().Get(parent.Id);
foreach (TChildEntity child in currentParent.GetChildren<TChildEntity>())
{
child.ForeignKey = adoptee.Id;
}
}
This seems to work correctly and without faults (minimal testing) are there any major flaws in doing this?
Thanks.
Edit Two
This is the OnModelCreating Method in the DbContext, which sets up the foreign key for each entity. Is this problematic?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
}
According to the updated example, you want to hide the explicit FK from the entity class public interface, and still let it be visible to EF Core and mapped to the FK column in the database.
The first problem is that the explicitly implemented interface member is not directly discoverable by EF. Also it has no good name, so the default conventions don't apply.
For instance, w/o fluent configuration EF Core will correctly create one to many associations between Parent and Child entities, but since it won't discover the int IChild<Parent>.ForeignKey { get; set; } properties, it would maintain the FK property values through ParentOneId / ParentTwoId shadow properties and not through interface explicit properties. In other words, these properties will not be populated by EF Core and also not considered by the change tracker.
To let EF Core use them you need to map both FK property and database column name using respectively HasForeignKey and HasColumnName fluent API method overloads accepting string property name. Note that the string property name must be fully qualified with the namespace. While Type.FullName provides that string for non-generic types, there is no such property/method for generic types like IChild<ParentOne> (the result has to be "Namespace.IChild<Namespace.ParentOne>"), so let first create some helpers for that:
static string ChildForeignKeyPropertyName<TParent>() where TParent : ParentEntity
=> $"{typeof(IChild<>).Namespace}.IChild<{typeof(TParent).FullName}>.{nameof(IChild<TParent>.ForeignKey)}";
static string ChildForeignKeyColumnName<TParent>() where TParent : ParentEntity
=> $"{typeof(TParent).Name}Id";
The next would be creating a helper method for performing the necessary configuration:
static void ConfigureRelationship<TChild, TParent>(ModelBuilder modelBuilder)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
var childEntity = modelBuilder.Entity<TChild>();
var foreignKeyPropertyName = ChildForeignKeyPropertyName<TParent>();
var foreignKeyColumnName = ChildForeignKeyColumnName<TParent>();
var foreignKey = childEntity.Metadata.GetForeignKeys()
.Single(fk => fk.PrincipalEntityType.ClrType == typeof(TParent));
// Configure FK column name
childEntity
.Property<int>(foreignKeyPropertyName)
.HasColumnName(foreignKeyColumnName);
// Configure FK property
childEntity
.HasOne<TParent>(foreignKey.DependentToPrincipal.Name)
.WithMany(foreignKey.PrincipalToDependent.Name)
.HasForeignKey(foreignKeyPropertyName);
}
As you can see, I'm using EF Core provided metadata services to find the names of the corresponding navigation properties.
But this generic method actually shows the limitation of this design. The generic constrains allow us to use
childEntity.Property(c => c.ForeignKey)
which compiles fine, but doesn't work at runtime. It's not only for fluent API methods, but basically any generic method involving expression trees (like LINQ to Entities query). There is no such problem when the interface property is implemented implicitly with public property.
We'll return to this limitation later. To complete the mapping, add the following to your OnModelCreating override:
ConfigureRelationship<ChildOne, ParentOne>(modelBuilder);
ConfigureRelationship<ChildOne, ParentTwo>(modelBuilder);
And now EF Core will correctly load / take into account your explicitly implemented FK properties.
Now back to limitations. There is no problem to use generic object services like your AdoptAll method or LINQ to Objects. But you can't access these properties generically in expressions used to access EF Core metadata or inside LINQ to Entities queries. In the later case you should access it through navigation property, or in both scenarios you should access in by the name returned from the ChildForeignKeyPropertyName<TParent>() method. Actually queries will work, but will be evaluated locally thus causing performance issues or unexpected behaviors.
E.g.
static IEnumerable<TChild> GetChildrenOf<TChild, TParent>(DbContext db, int parentId)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
// Works, but causes client side filter evalution
return db.Set<TChild>().Where(c => c.ForeignKey == parentId);
// This correctly translates to SQL, hence server side evaluation
return db.Set<TChild>().Where(c => EF.Property<int>(c, ChildForeignKeyPropertyName<TParent>()) == parentId);
}
To recap shortly, it's possible, but use with care and make sure it's worth for the limited generic service scenarios it allows. Alternative approaches would not use interfaces, but (combination of) EF Core metadata, reflection or Func<...> / Expression<Func<..>> generic method arguments similar to Queryable extension methods.
Edit: Regarding the second question edit, fluent configuration
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);
modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
produces the following migration for ChildOne
migrationBuilder.CreateTable(
name: "ChildOne",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ForeignKey = table.Column<int>(nullable: false),
Name = table.Column<string>(nullable: true),
Balance = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChildOne", x => x.Id);
table.ForeignKey(
name: "FK_ChildOne_ParentOne_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentOne",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ChildOne_ParentTwo_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentTwo",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Note the single ForeignKey column and the attempt to use it as foreign key to both ParentOne and ParentTwo. It suffers the same problems as using a constrained interface property directly, so I would assume it not working.
I'm currently trying to write to a table which inherits from an abstract base class. When I try to do this I get the following error (The ContactMethod property is the discriminator):
System.Data.SqlClient.SqlException: Invalid column name 'ContactMethod'.
EmailContactDetails.cs:
public class EmailContactDetail : ContactDetail
{
[ApiMember(Description = "The Contact Method")]
public override ContactMethod ContactMethod => ContactMethod.Email;
[ApiMember(Description = "Email Address")]
public string EmailAddress { get; set; }
}
EmailContactDetailConfiguration.cs:
public class EmailContactDetailsConfiguration : IEntityTypeConfiguration<EmailContactDetail>
{
public void Configure(EntityTypeBuilder<EmailContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<EmailContactDetail> builder, string schema)
{
builder.Property(x => x.EmailAddress).HasColumnName("EmailAddress").HasColumnType("nvarchar(255)");
}
}
ContactDetail.cs:
public abstract class ContactDetail
{
[ApiMember(Description = "The Identifier")]
public Guid Id { get; set; }
[ApiMember(Description = "The Contact Method")]
public virtual ContactMethod ContactMethod { get; set; }
}
ContactDetailConfiguration.cs
public class ContactDetailsConfiguration : IEntityTypeConfiguration<ContactDetail>
{
public void Configure(EntityTypeBuilder<ContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<ContactDetail> builder, string schema)
{
builder.ToTable("ContactDetails", schema);
// Table per hierarchy. all subclasses share the same db table for performance.
builder.HasDiscriminator(x => x.ContactMethod)
.HasValue<EmailContactDetail>(ContactMethod.Email);
builder.Property(x => x.Id).HasColumnName("Id").IsRequired().HasColumnType("uniqueidentifier").ValueGeneratedOnAdd();
}
}
I've tried hiding the discriminator "ContactMethod" by adding the following to the ContactDetailConfiguration.cs file:
builder.Ignore(x => x.ContactMethod);
Once I've done that I end up with the following error
The entity type 'EmailContactDetail' is part of a hierarchy, but does not have a discriminator property configured.
You shouldn't hide the property configured as TPH discriminator from EF because it is essential for EF Core implementation of the TPH strategy.
The initial error simply indicates that your model and database are out of sync. It's true that by convention EF Core uses string shadow property and column called Discriminator. But the whole purpose of HasDiscriminator fluent API is to allow changing the discriminator property/column type, as well as mapping it to an existing property of your entity model.
Which is the case here. You've told EF Core to use your existing property ContactMethod as discriminator, hence EF Core is looking for column named ContactMethod in the database table. So to resolve the issue, simply update your database from the model (using the usual procedure when model is changed - add new migration, update database etc).
I work on a framework with EF. I want to get all ignored properties of an entity to build some special queries. How can I do it?
public class Customer
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
public int Age { get; set; }
}
public class CustomerContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
public static class DbContextExtensions
{
public static List<string> GetIgnoredProperties(this DbContext context, string entityTypeName)
{
// ???
}
}
I know this is not answering your original question, and in my comments I mentioned that you should use reflection, but that was only because I read your question wrong.
Here is an alternative using reflection, for if you do not come right.
If you assign the [NotMapped] attribute to the properties on your class that you would like to ignore, you could possibly retrieve all [NotMapped] properties using reflection. Below is an example of how this could be achieved.
var resultArray = yourClassInstance.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(NotMappedAttribute)));
Hope this helps you in some way.
You can achieve what you want by calling the DbModelBuilder.Build. It will create a DbModel base on configuration setup by the DbModelBuilder. The DbModel expose a ConceptualModel that hold the types used by the context. The EdmModel hold each type that are declared in the context, and for each type, it hold the properties that has not been ignored by the DbModelBuilder during it's configuration. So, to achieve what you want, you have to intersect the properties of each entity type with those present in the EdmModel. It will give the delta between them, thefore the ignored properties. Here an example :
public class CustomerContext : DbContext
{
private static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> _ignoredProperties;
/// Hold the ignored properties configured from fluent mapping
public static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> IgnoredProperties
{
get
{
return _ignoredProperties;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
// Build ignored properties only if they are not
if (_ignoredProperties == null)
{
var model = modelBuilder.Build(this.Database.Connection);
var mappedEntityTypes = new Dictionary<Type, IReadOnlyCollection<PropertyInfo>>();
foreach (var entityType in model.ConceptualModel.EntityTypes)
{
var type = Type.GetType(entityType.FullName);
var typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var mappedProperties = entityType.DeclaredProperties.Select(t => t.Name)
.Union(entityType.NavigationProperties.Select(t => t.Name));
mappedEntityTypes.Add(type, new ReadOnlyCollection<PropertyInfo>(
typeProperties.Where(t => !mappedProperties.Contains(t.Name)).ToList()));
}
_ignoredProperties = new ReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>>(mappedEntityTypes);
}
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
The IgnoreProperties property is a singleton that will be initialized the first time you will use the context. It will be null before that, so will have to ensure that nothing use it until it's initialized. It's readonly, so you don't have to worrie about accidental clear of the collection. The entity type is used as key, and the value expose a collection that hold ignored properties. Example of use :
var properties = CustomerContext.IgnoredProperties[typeof(Customer)];
Cons :
With this approach is that the DbModel will be built twice, one time to gather the ignored properties, and second time by EntityFramework when the DbCompiledModel will be cached for futur ObjectContext creation. It can have an impact on the cold start of the DbContext, it means that the fist time you will execute a query over your context, it will be a bit slower. It will depend on the size of the DbContext. Warm queries should not suffer. OnModelCreating will be called once anyway.
Pros :
All changes made on de DbModelBuilder configuration will be automatically reflected in the IgnoredProperties property.
So I'm using the 'CodeFirst' methodology of Entity Framework and I have mapping files to map the table information and add in things such as validation so for instance:
this.Property(t => t.AccountName)
.IsRequired()
.HasMaxLength(25);
This is using the Fluent API and I'm wondering how to get the property name by string instead of t.AccountName. I'm wanting to dynamically set these properties and I just don't know how to do that programmatically.
Without commenting on whether this is advisable or not(!), you can achieve what you need because the Property() method takes an expression tree as its parameter. Consider the following:
public class MyEntity
{
[Key]
public int MyEntityId { get; set; }
public string MyProperty { get; set; }
}
public class MyContext : DbContext
{
public DbSet<MyEntity> MyEntities
{
get { return this.Set<MyEntity>(); }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var param = Expression.Parameter(typeof(MyEntity));
var propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property((Expression<Func<MyEntity, string>>)propertyExpression)
.HasColumnName("Fish");
}
}
Here I build configuration for the MyProperty column, which I refer to by name in a lambda expression.
The above code works for string properties, but would require some modification to work for any property type. The cast to Expression<Func<MyEntity, string>> hard-codes the property type, but we can eliminate the cast using the dynamic language feature.
var param = Expression.Parameter(typeof(MyEntity));
dynamic propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property(propertyExpression)
.HasColumnName("FishFace");