Emit DbContext.OnModelCreating on every context creation - c#

I use entity framework code first to work with my database.
I have several tables with different names but same structure, and this tables dynamically appears in database. How could I map EntityFramework to one of that tables at run-time and use data from just like I work this over entities of DbContext?
What I've done to make it work:
For example, my class what describes structure of dynamically created table is SetElement.
Here is my context:
public class DataContext : DbContext
{
public DataContext()
: base("RepositoryConnectionString") { }
string setElementsTableId; // the name of table that need to be dynamicly mapped to
// Enforce model recreating
public DataContext(string setElementsTableId)
: this()
{
this.setElementsTableId = setElementsTableId;
}
/* some other entities */
public DbSet<Entities.SetElement> SetElements { get; set; } // dynamicly mapped entity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* come configurations */
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId)); // map SetElements property to dynamicly created table
}
}
}
How I use this:
public static void AddSetElements(ICollection<SetElement> setElements, string tableId)
{
using (ctx = new DataContext(tableId)) // configere DataContext to map tableId table for entity SetElements
try
{
var num = ctx.SetElements.Count();
ctx.SetElements.AddRange(setElements);
ctx.SaveChanges();
}
catch (Exception e)
{
}
}
I have also some methods to get, udtate and remove data from dynamicly created tables that are same to AddSetElements.
All works just as I wish but only if AddSetElements runs first, because at the first datacontext creating DbContext.OnModelCreating runs and configure all mappings. But next instance creation doesn't call DbContext.OnModelCreating.
So, my question is: how to call DbContext.OnModelCreating everytime of creating an instance of DataContext then I use DataContext(string setElementsTableId) to create it?
I know, my question is similar to 'dynamic table mapping in EF' but I found nothing in the results.
By the way. If you know another way to solve my problem, you are welcome.

There is a built-in feature which may address your issue : `IDbModelCacheKey ; the implementation of which is to be registered in your configuration.
The point is to generate a different key for your different contexts.
I would go for something like :
First, the configuration
public class EntityFrameworkConfiguration: DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
Then the implementation of the IDbModelCacheKey
public class EntityModelCacheKey : IDbModelCacheKey
{
private readonly int _hashCode;
public EntityModelCacheKey(int hashCode)
{
_hashCode = hashCode;
}
public override bool Equals(object other)
{
if (other == null) return false;
return other.GetHashCode() == _hashCode;
}
public override int GetHashCode()
{
return _hashCode;
}
}
Finally, your DataContext
public class DataContext : DbContext
{
string setElementsTableId;
// use the setElementsTableId as extended property of the
// connection string to generate a custom key
public DataContext(string setElementsTableId)
: base(ConfigurationManager.ConnectionStrings["RepositoryConnectionString"]
+ "; Extended Properties=\"setElementsTableId=" + setElementsTableId + "\"")
{
this.setElementsTableId = setElementsTableId;
}
public DbSet<Entities.SetElement> SetElements { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId));
}
}
}
I hope this will be of some help

Look like nobody knows answer...
Otherwise, one man told me that my question is meaningless because of storage data in several tables will not give any achievement. More better to add indexes to database, partitioning table or something else. In other words this is Database Management System problem. But if some one knows answer I'll be very pleasured to hear something about EF hack.

Related

Why doesn't Entity Framework recognise an existing record in the database?

I have created a database system for a library, and when a user registers the program checks if they already have an account, but this is always returning false even when I can see in my DBMS that the record exists.
public static bool UserExists(string email)
{
using (var context = new LibraryDbContext())
{
if (context.Users.Any(d => d.Email == email))
{
return true;
}
else
{
return false;
}
}
}
Also, this is my Dbcontext class
public class LibraryDbContext : DbContext
{
public LibraryDbContext()
: base()
{
}
public DbSet<Book> Books { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<ReturnsLog> Returns { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("LibraryProjectDbo");
}
}
Typically issues like this when connecting to an existing database for the first time are due to EF being pointed at a database you don't expect. (I.e. creating one via Code First rather than connecting to your existing DB, or creating tables you don't expect.)
When using database first, you should always disable the Code First initialization:
public LibraryDbContext()
{
Database.SetInitializer<LibraryDbContext>(null);
}
This will generate an exception if the DbContext goes and tries to create a schema which will help direct you to the correction.
By default EF will be looking for a connection string called "LibraryDbContext" in your web.confing/.exe.config file in the runtime location. When using connection strings I generally like to be explicit with the connection string name to reflect a Database rather than the DbContext. Often I will utilize multiple contexts to split up behaviour in a system that all point to the same database. So for example I'd call the main database connection string something like "AppConnection" and pass that through the base constructor.
public LibraryDbContext()
: base ("AppConnection")
{
Database.SetInitializer<LibraryDbContext>(null);
}
Alternatively to test your connection out you can pass a Connection String itself to the base constructor.

What is the Entity Framework Core attribute equivalent to modelBuilder's HasDefaultValueSql?

I want to use annotations for setting the default value for my properties in Entity Framework Core. The issue is that the database is not setting the default values so the value is not being passed down to the database layer.
I want to do something similar to modelBuilder's HasDefaultValueSql:
[DefaultValue("400")]
public int LengthInMeters {get; set;}
How do you convert the below code to attributes?
modelBuilder.Entity<Patient>().Property(c => c.LengthInMeters).HasDefaultValueSql("400");
Using default values by themselves doesn't work. I want to use attributes alone without having to mess with the migrations.
Problems: I've tried other methods with EF but Entity Framework Core doesn't have some items. Such as modelBuilder.Conventions nor AttributeToColumnAnnotationConvention nor CSharpMigrationCodeGenerator nor modelBuilder.Properties()
This is what I ended up doing, if someone has a cleaner not as intensive way of implementation let me know.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
var memberInfo = property.PropertyInfo ?? (MemberInfo)property.FieldInfo;
if (memberInfo == null) continue;
var defaultValue = Attribute.GetCustomAttribute(memberInfo, typeof(DefaultValueAttribute)) as DefaultValueAttribute;
if (defaultValue == null) continue;
property.SqlServer().DefaultValue = defaultValue.Value;
}
}
}
I can set the default value in the database using the default value attribute
[DefaultValue("400")]
public int LengthInMeters {get; set;}
Struggled a while getting this job done in another way using EF-Core conventions. I discovered a way to add so called "Plugins" which implement the IConventionSetPlugin interface with which you can add custom conventions. It needs some additional code to get EntityFramework to use the plugin.
But first things first, let's create our PropertyAttributeConvention.
public class DefaultValueAttributeConvention : PropertyAttributeConventionBase<DefaultValueAttribute>
{
public DefaultValueAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { }
protected override void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, DefaultValueAttribute attribute,
MemberInfo clrMember, IConventionContext context)
{
propertyBuilder.HasDefaultValue(attribute.Value, fromDataAnnotation: true);
}
}
Here we just say the ef propertybuilder to use the default value defined in our [DefaultValue] attribute.
To add the convention we need to create a custom plugin class:
public class CustomConventionSetPlugin : IConventionSetPlugin
{
public ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.PropertyAddedConventions.Add(new DefaultValueAttributeConvention(null));
return conventionSet;
}
}
For our plugin to get used, we have to create an ef extension class (which itself contains another ExtensionInfo class)
public class CustomDbContextOptionsExtension : IDbContextOptionsExtension
{
public void ApplyServices(IServiceCollection services)
{
services.AddSingleton<IConventionSetPlugin, CustomConventionSetPlugin>();
}
public void Validate(IDbContextOptions options) { }
public DbContextOptionsExtensionInfo Info => new CustomDbContextOptionsExtensionInfo(this);
private class CustomDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo
{
public CustomDbContextOptionsExtensionInfo(IDbContextOptionsExtension extension) : base(extension) { }
public override long GetServiceProviderHashCode() => 0;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => "";
}
}
In the extension class we're adding our plugin class to the EF-ServiceCollection.
The last step is to go to our DbContext and add our extension. This can be done in the OnConfigure method:
public class MyDatacontext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(new CustomDbContextOptionsExtension());
}
}
Now the [DefaultValue] attribute can be used on our entity properties.
If we want to add different custom conventions we dont have to create all that extension/plugin classes again. Just create a new convention class and add it through our existing plugin class to the convetionSet.
Install Microsoft.EntityFrameworkCore.Relational package, it should solve most of your migration issues when moving to EF core.

Fluent NHibernate automap PostGIS geometry type

Given the following model:
using NetTopologySuite.Geometries;
public class bounding_box
{
public virtual int id { get; protected set; }
public virtual Polygon area { get; set; }
}
How do I automap the area property to a area geometry(Polygon) column when generating the DB schema using Fluent Nhibernate? Note that I do not care about being able to read / update the geometry column using NHibernate since I will be using GDAL in my code.
I know I can do it by implementing a manual override, i.e.:
public class bounding_boxMappingOverrride : IAutoMappingOverride<bounding_box>
{
public void Override(AutoMapping<bounding_box> mapping)
{
mapping.Map(x => x.area)
.CustomSqlType("geometry(Polygon)");
}
}
However, I have many tables with geometry columns so I would much prefer to be able to specify a custom type mapping.
For some reason, the area property is never intercepted by the following property convention:
public class PostgisTypesConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
if (instance.Type == typeof(Polygon))
{
instance.CustomSqlType("geometry(Polygon)"); // Never reached
}
}
}
I have the same problem if I use GeoAPI.Geometries.IPolygon instead of NetTopologySuite.Geometries.Polygon...
I was finally able to resolve this by defining a custom UserTypeConvention, i.e.:
using NetTopologySuite.Geometries;
using NHibernate.Spatial.Type;
public class PostGisPolygonUserTypeConvention : UserTypeConvention<PostGisGeometryType>
{
public override void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(c => c.Type == typeof(Polygon));
}
public override void Apply(IPropertyInstance instance)
{
// Have to set CustomType to be able to read/write rows using NHibernate
instance.CustomType<PostGisGeometryType>();
// Have to set CustomSqlType to generate correct SQL schema
instance.CustomSqlType("geometry(Polygon)");
}
}
The same principle can also be used to create UserTypeConventions for other geometries, such as Point, LineString, MultiPoint, etc.

DataSet ToList method returns empty List, even though it has elements

Sorry if somebody has already asked this question. I've been looking around but couldn't find anything related.
So, I am using Entity Framework and I am trying to load the list of "Idiomas" from the dataset using the ToList method through the following code:
//List<Idioma> ans = new List<Idioma>(contexto.Idiomas);
return contexto.Idiomas.ToList();
However, the method is returning an empty list, even though I can see through the debugger that the DataSet has elements.
EDIT
I have not put any extra code because there is not any extra code besides those two lines.
I just create a Entity Framework model-first, generated the database, and updated the model from the database to make sure everything was ok.
Contexto is a instance from the class LivroContexto, which implements DbContext (auto generated by vs2012, see below).
LivroContainer:
public partial class LivroContainer : DbContext
{
public LivroContainer() : base("name=LivroContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public DbSet<Idioma> Idiomas { get; set; }
}
Idiomas:
public partial class Idioma
{
public Idioma()
{
this.Traducaos = new HashSet<Traducao>();
}
public int IdIdioma { get; set; }
public string Lingua { get; set; }
public virtual ICollection<Traducao> Traducaos { get; set; }
}
Thank you
nobody can see, why your code is not working, from the amount of code you have posted.
however, make sure following :
the class of the instance contexto inherits DbContext or any child class inheriting DbContext.
make sure, in case, you have not generated the database, through the code,rather mapped an existing database, to a codefirst frontend, that DbSet<Idioma> Idiomas is mapped to proper table. ie.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Idioma>().ToTable("IdiomaTableNameInDatabase");
}
make sure, your connectionString name, that you passed in the DbContext base constructor is present in the web.config, and if you have not passed anything, make sure a connectionString with the name of your DbContext inheriting class is there.
If all these are correct, there is absolutely no reason, why contexto.Idiomas.ToList() won't return anything.
and also, say, your context class is MyContext, then it should be like this:
public class MyContext:DbContext
{
public MyContext:base("ConnectionStringName")
{
}
//--dbSet properties
public DbSet<Idioma> Idiomas{get;set;}
//other overridden methods
}
which you use, on your upper layers like this:
public List<Idioma> GetAllIdiomas()
{
MyContext contexto = new MyContext();
return contexto.Idiomas.ToList();
}

Change entity map to another "unknown" table at runtime

I have to write a C# application that works with a SQL server database created and mantained by an old application. The application creates new tables each year and the "year property" is in the table name. The number of tables it creates may vary depending of the number of "sections" that the user has created inside the application. So, I have to work with tables like Cwx_DRyz (quite self explanatory...), where "wx" can be the section, and "yz" would be the year. An example of group of table could be:
C01_DR07
C01_DR08
C01_DR09
C02_DR08
C02_DR09
C03_DR06
C04_DR12
And all of those tables could represent, for example, clients. They would be clients from different sections and years, but clients with the same structure.
My question is: Can I have a Client entity to handle all those tables and change the mapping from one to another at runtime? The title says "unknown" because I don't know the tables before runtime.
The most similar question I have found is Entity Framework map multiple tables to one entity and the answer is to use the "Table Per Concrete Type Inheritance", but it is not useful for my case.
PS: EF version 4.3.1 and VS2010
EDIT: The tables don't have primary keys... Most of them have columns that are supossed to have unique values (integer or string).
If you use "code first" you could create a mapping as you want. This also works with existing databases when the mapping you have created match the database.
So whenever you create a context you can build the string (tablename) you want to map to.
Some codesamples for "code first" and how you could start:
The DbContext:
public DbSet<YourEntity> YourEntities { get; set; }
...
// this is called when the db gets created and does the configuration for you => maybe not needed in your case
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
ConfigurationRegistrar configurationRegistrar = modelBuilder.Configurations;
new GeneralEntitiesConfiguration(configurationRegistrar);
}
GeneralEntitiesConfiguration is a class im using to handle the configurations, nothing more than a helper which looks like:
public class GeneralEntitiesConfiguration
{
public GeneralEntitiesConfiguration(ConfigurationRegistrar configurationRegistrar)
{
configurationRegistrar.Add(new YourEntityConfiguration());
//and additional configurations for each entity, just to splitt it a bit and have it more read and maintenance able
}
}
YourEntityConfiguration is a class where i have all the configurations for this entity:
public class YourEntityConfiguration : EntityTypeConfiguration<YourEntity>
{
public YourEntityConfiguration ()
{
ToTable("WhatEverYouLike"); // here you can do any magic to map this entity to a table, just make sure that your properties are mapped to the correct colums
Property(entity => entity.Id).HasColumnName("YouColumnName");
//and here you also have to do the other configurations
}
}
At the application startup (or before you initialize your context the first time) you have to initialize the database. Therefore you can use an initializer which checks the database and handles differences. Build in there are things like "DropCreateDatabaseAlways" or "DropCreateDatabaseIfModelChanges" => you would need to create your own which just ignores any differences. In my sample i have create one which just throws an exception when the model differs (i wanted to handle model changes with scipts for the first try):
//before using the context the first time i'm calling, you can ignore the connection string
DbContextInitializer.Init(conString);
public static class DbContextInitializer
{
public static void Init (string connectionString)
{
Database.SetInitializer(new CreateDbThrowExceptionIfModelDiffersInitializer<SMDbContext>());
using(var dbContenxt = new MyDbContext(connectionString))
{
try
{
dbContenxt.Database.Initialize(true);
}
catch(DatabaseModelDiffersException diffException)
{
// some magic...
}
catch(Exception ex)
{
// TODO: log
throw;
}
}
}
public class CreateDbThrowExceptionIfModelDiffersInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
using (new TransactionScope(TransactionScopeOption.Suppress))
{
if (!context.Database.Exists())
context.Database.Create();
}
if (!context.Database.CompatibleWithModel(true))
{
throw new DatabaseModelDiffersException("Database Model differs!");
}
}
protected virtual void Seed(TContext context)
{
// create data if you like
}
}
// just an exception i'm using for later useage
public class DatabaseModelDiffersException : Exception
{
public DatabaseModelDiffersException(string msg) : base(msg)
{}
}
}
Hope you have got an idea of you can handle dynamic table names with entity framework!
If there are more questions just ask ;)

Categories