We are using entity framework 6.1.1 with a DbContext like below and EntityTypeConfiguration to map approximately 400 entities to our DbContext. We then create an instance of our DbContext and use it to create the object sets for each IRepository entity we use in our service layer. The problem we are having which I cannot find a solution to is that the first call to the db is taking approximately 18 seconds when we are using Ants profiler.
I have looked into generating the views but I cannot find a way to do that when the DbContext does not contain hard-coded DbSet collections to the entities. Is there a way to pre-generate the views with our pattern and if so will we see a significant performance improvement?
Or is it time to go down a different path, should we create smaller DbContexts which are for specific areas of the database on logical separations?
public class Context: DbContext
{
#pragma warning disable
Type dummyType_SqlProviderServices = typeof(System.Data.Entity.SqlServer.SqlProviderServices);
#pragma
static Context()
{
Database.SetInitializer(new ContextatabaseInitializer<Context>());
}
public Context(DbConnection con)
: base(con, false)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.AddFromAssembly(typeof(ZincContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}
public class EntityRepository<T> : IEntityRepository<T> where T : class
{
protected IDbSet<T> ObjectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this.DbContext.Set<T>();
}
return _objectSet;
}
}
}
Related
I have a project with multiple DbContext classes: one for every domain. Since EF Core isn't perfect I have some functionality I have to add to every DbContext like allowing it to handle the new JSON functionality of SQL Server 2017. There is other functionality I'm adding too but for the sake of brevity I have removed it from the example.
To add this JSON functionality to every DbContext I work with a BaseDbContext which is an abstract class that simply extends the DbContext. One of the prerequisites for the functionality is interception of SQL queries that are sent to the database. To perform this I make use of the tactic described here.
In the next few weeks we are moving into different development environments and I want to inject the correct connection string based on the current environment through the Options pattern. This doesn't play well with the interception tactic. Since to perform interception GetService<DiagnosticSource>() is called which actually triggers the OnConfiguring function BEFORE the continuation of the constructor of my SomeDbContext, which means my options are never filled in leading to a NullReferenceException.
How can I extend DbContext without triggering OnConfiguring while at the same time avoiding code duplication? I can also run a custom EF Core but I'd like to avoid that if at all possible.
public abstract class BaseDbContext : DbContext, IBaseDbContext
{
protected BaseDbContext()
{
DiagnosticListener listener = this.GetService<DiagnosticSource>() as DiagnosticListener; // This calls OnConfiguring.
listener.SubscribeWithAdapter(new JsonInterceptor());
}
[DbFunction("JSON_VALUE", "")]
public static string JsonValue(string source, string path)
{
throw new NotSupportedException();
}
[DbFunction("TRY_CAST", "")]
public static string TryCast(string source, string typeName)
{
throw new NotSupportedException();
}
}
public class SomeDbContext : BaseDbContext, ISomeDbContext
{
private readonly SomeDbContextOptions _options;
public DbSet<Order> Orders { get; set; }
public SomeDbContext(IOptions<SomeDbContextOptions> options)
{
_options = options.Value;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// This is called before _options is set meaning it is null in the line below.
optionsBuilder
.UseSqlServer(_options.ConnectionString);
}
}
I just started moving my MVC5 project with EF6x to MVC Core and EF Core but have a big problem with my entities configuration's. How you can migrate a EF6 Fluent configure to EF core?
I need a guide with sample if possible.
Here is one of my mapping classes and my try
EntityMappingConfiguratuin
public interface IEntityMappingConfiguration
{
void Map(ModelBuilder b);
}
public interface IEntityMappingConfiguration<T> : EntityMappingConfiguration where T : class
{
void Map(EntityTypeBuilder<T> builder);
}
public abstract class EntityMappingConfiguration<T> : EntityMappingConfiguration<T> where T : class
{
public abstract void Map(EntityTypeBuilder<T> b);
public void Map(ModelBuilder b)
{
Map(b.Entity<T>());
}
}
public static class ModelBuilderExtenions
{
private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
{
return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
}
public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
{
var mappingTypes = assembly.GetMappingTypes(typeof(IEntityMappingConfiguration<>));
foreach (var config in mappingTypes.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
{
config.Map(modelBuilder);
}
}
}
DbContext
public class CommerceServiceDbContext : AbpDbContext
{
public CommerceServiceDbContext(DbContextOptions<CommerceServiceDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
}
}
Simple old configuration
public partial class AffiliateMap : EntityMappingConfiguration<Affiliate>
{
public override void Map(EntityTypeBuilder<Affiliate> b)
{
b.ToTable("Affiliate");
b.HasKey(a => a.Id);
b.HasRequired(a => a.Address).WithMany().HasForeignKey(x => x.AddressId).WillCascadeOnDelete(false);
}
}
My Try
public partial class AffiliateMap : EntityMappingConfiguration<Affiliate>
{
public override void Map(EntityTypeBuilder<Affiliate> b)
{
b.ToTable("Affiliate");
b.HasKey(a => a.Id);
b.HasOne(a => a.Address)
.WithMany().HasForeignKey(x => x.AddressId).IsRequired().OnDelete(DeleteBehavior.Restrict);
}
}
I've done this using Google Search and Microsoft Documentation. But I'm not sure of my work. Since I have +100 configure classes, I'll ask you before continuing. I apologize if the contents of my question are not compatible with the terms and conditions of the site.
I found a good article about moving to EF core. I want share that and keeping this question for starters like me.
Code Updates
Namespace System.Data.Entity replaced by Microsoft.EntityFrameworkCore
HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) replaced by ValueGeneratedNever();
The base constructor of DbContext doesn't have a single string parameter for the connection string. We now have to inject the DbContextOptions
OnModelCreating(DbModelBuilder modelBuilder) becomes OnModelCreating(ModelBuilder modelBuilder). Simple change, but change all the same
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly()); is no longer available which means that EntityTypeConfiguration is also not available, so I had to move all my entity configuration to OnModelCreating
((IObjectContextAdapter)context).ObjectContext.ObjectMaterialized is no longer available. I was using that to extend the DbContext to convert all dates in an out to Utc. I haven't found a replacement for that yet.
ComplexType is no longer available. I had to change the model structure a bit to accomodate this.
MigrateDatabaseToLatestVersion is no longer available so I had to add the below to my startup.cs
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetService<SbDbContext>().Database.Migrate();
}
WillCascadeOnDelete(false) becomes OnDelete(DeleteBehavior.Restrict)
HasOptional is no longer relevant as per post
IDbSet becomes DbSet
DbSet<T>.Add() no longer returns T but EntityEntry<T>
var entry = context.LearningAreaCategories.Add(new LearningAreaCategory());
//that's if you need to use the entity afterwards
var entity = entry.Entity;
IQueryable<T>.Include(Func<>) now returns IIncludableQueryable<T,Y> instead of IQueryable<T>, same applies for OrderBy. What I did was moving all the includes and orderbys to the end.
Source: Moving from EF6 to EF Core
As MSDN suggests we can use the following ObjectContext
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
// Add the new object to the context.
context.Products.AddObject(newProduct);
}
However there is also another use of a similiar code with using ObjectSet<T>
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
ObjectSet<Product> pSet = context.CreateObjectSet<Product>();
pSet.AddObject(newProduct);
}
The second paragraph in the article says:
In versions starting with .NET Framework version 4, you can use the
following methods defined on ObjectSet instead of the equivalent
ones defined on ObjectContext.
Is there a spesific reason to use ObjectSet instead of ObjectContext and how do we know which to use when?
ObjectContext and ObjectSet are legacy EF code, for which DbSet and DbContext have created as wrappers around ObjectContext model to make EF a much better experience.
Underneath DbSet and DbContext, EF is still using ObjectContext / ObjectSet.
Starting with EF 7, they got rid of all base code, and are re writing the whole EF ORM.
EDIT
DbContext = A collection of your entity models, connection to the database, logging, tracking, and glue, and probably a whole bunch of things i have missed. This usuall contains 1 or more DbSets<YourEntity>
DbSet is an object representing a collection of a specific entity. This contains information such as caching, inserting, updating, selecting for only a specific entity.
I like to think of these a
DbContext = Database
DbSet = Table
They are ALOT more than that, but conceptually that is how I visualise them, and don't necessarily map 1:1. E.g. an Entity may be a subset of a table or it may even be a combination of multiple tables.
Regarding ObjectSet and ObjectContext I lack experience in how they work internally in order to tell you what the difference is exactly. I know how DbSet/Context works, but I don't know how much of it gets done by ObjectSet/Context and how much is additional.
Maybe an excercise for you to find out in the wild? :-P
There isn't much difference if you use ObjectSet directly.
However by using ObjectContext and OBjectSet together, you can develop reusable generic repository classes (CRUD). The code sample that you provided will only work for retrieving products for that application only, whereas a generic CRUD repository would define methods to Add, Read, Update and Delete which can work with any table (and in other databases).
e.g.
You can define a IRepository interface
public interface IRepository<T> : IDisposable where T : class
{
void Add(T entity);
void Delete(T entity);
void SaveChanges();
...
}
And a generic concrete class
public class DataRepository<C,T> : IRepository<T> where T : class where C : ObjectContext, new()
{
private ObjectContext _context;
private IObjectSet<T> _objectSet;
public DataRepository() : this(new C()) { }
public DataRepository(ObjectContext context)
{
_context = context;
_objectSet = _context.CreateObjectSet<T>();
}
public void Add(T entity)
{
if(entity == null) throw new ArgumentNullException("entity");
_objectSet.AddObject(entity);
}
public void Delete(Func<T, bool< predicate)
{
var records = from x in _objectSet.Where<T>(predicate) select x;
foreach(T record in records)
_objectSet.DeleteObject(record);
}
public void SaveChanges()
{
_context.SaveChanges();
}
// Other members
// IDisposable members
}
The code above you could copy and paste to each application, or put in a separate assembly and reference in each application.
For your example, you would create this class in your application to retrieve products
public class ProductsRepo : DataRepository<AdventureWorksEntities, Product> {
// You can add other specific methods not covered by the default CRUD methods here
}
And to add a new product
using(var repo = new ProductsRepo())
{
repo.Add(newProduct);
repo.SaveChanges();
}
I am new to Code first Entity framework, when logging into the database after running my app for the first time I got a little confused when I saw the "__MigrationHistory" table.
I now understand the need for this table, but do not like it being in the standard dbo schema within the user table, I think its obtrusive and a risk.
My first thought was to move it to the system folder. When researching how to achieve this within the EF context all I could find is how to move it from system to dbo.
I now get the feeling __MigrationHistory should by default be created within the system folder... is this the case?
How can I configure my context to manage/reference the migration history table within the system folder by default?
Here is my context, am I doing something wrong or missing some configuration?
public class MyContext : DbContext, IDataContext
{
public IDbSet<Entity> Entities { get; set; }
public MyContext()
: base("ConnectionString")
{
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
There is a technique for moving __MigrationHistory. That table has it's own context (System.Data.Entity.Migrations.History.HistoryContext) that you can override:
public class MyHistoryContext : HistoryContext
{
public MyHistoryContext(DbConnection dbConnection, string defaultSchema)
: base(dbConnection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<HistoryRow>().ToTable(tableName: "MigrationHistory", schemaName: "admin");
modelBuilder.Entity<HistoryRow>().Property(p => p.MigrationId).HasColumnName("Migration_ID");
}
}
Then you need to register it:
public class ModelConfiguration : DbConfiguration
{
public ModelConfiguration()
{
this.SetHistoryContext("System.Data.SqlClient",
(connection, defaultSchema) => new MyHistoryContext(connection, defaultSchema));
}
}
You could try executing EXEC sys.sp_MS_marksystemobject __MigrationHistory in your seed method using context.Database.ExecuteSqlCommand();
I'm trying to create a base dbcontext that contains all the common entities that will always be reused in multiple projects, like pages, users, roles, navigation etc.
In doing so I have a ContextBase class that inherits DbContext and defines all the DbSets that I want. Then I have a Context class that inherits ContextBase where I define project specific DbSets. The classes are defined as follows:
public class ContextBase : DbContext
{
public virtual DbSet<User> Users { get; set; }
//more sets
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UsersConfiguration());
//add more configurations
}
}
public class Context : ContextBase
{
public DbSet<Building> Buildings { get; set; }
//some more project specific sets
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new BuildingsConfiguration());
//add more project specific configs
}
}
In my global.asax:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>());
where Configuration referes to a class inheriting DbMigrationsConfiguration and overriding the Seed method.
The two context classes are defined in the same namespace, but cross assembly (in order that I may update the base project in multiple existing projects without touching the project specific code) - not sure if this is relevant.
MY PROBLEM:
When running this code, it works fine, but when looking in the Database, it actually creates two different databases!! One containing all the base entity tables and one containing BOTH base and custom tables. CRUD operations are only performed on the custom version (which is obviousely what I want), but why does it create the schema of the other one as well?
Any help is appreciated, thanks!
UPDATE:
The following code is what I ended up with. It isn't ideal, but it works. I would still love to get feedback on ways to improve this, but in the meantime I hope this helps further the process. I REALLY DO NOT RECOMMEND DOING THIS! It is extremely error prone and very frustrating to debug. I'm merely posting this to see if there is any better ideas or implementations to achieve this.
One (but not the only) issue still existing is that the MVC views have to be manually added to projects. I've added it to the Nuget package, but it takes 2 to 3 hours to apply a nuget package with so many files when VS is connected to TFS. With some more work and a custom View engine the views can be precompiled (http://blog.davidebbo.com/2011/06/precompile-your-mvc-views-using.html).
The solution is split into the Base Framework projects and the Custom projects (each category includes its own models and repository pattern). The framework projects are packaged up in a Nuget package and then installed in any custom projects allowing the common functionality of any project like user, role and permission management, content management, etc (often referred to as the Boiler Plate) to be easily added to any new projects. This allows any improvements of the boilerplate to be migrated in any existing custom projects.
Custom Database Initializer:
public class MyMigrateDatabaseToLatestVersion : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
//create the base migrator
var baseConfig = new FrameworkConfiguration();
var migratorBase = new DbMigrator(baseConfig);
//create the custom migrator
var customConfig = new Configuration();
var migratorCustom = new DbMigrator(customConfig);
//now I need to check what migrations have not yet been applied
//and then run them in the correct order
if (migratorBase.GetPendingMigrations().Count() > 0)
{
try
{
migratorBase.Update();
}
catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
{
//if an error occured, the seed would not have run, so we run it again.
baseConfig.RunSeed(context);
}
}
if (migratorCustom.GetPendingMigrations().Count() > 0)
{
try
{
migratorCustom.Update();
}
catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
{
//if an error occured, the seed would not have run, so we run it again.
customConfig.RunSeed(context);
}
}
}
}
Framework's DB Migrations Configuration:
public class FrameworkConfiguration: DbMigrationsConfiguration<Repository.ContextBase>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
public void RunSeed(Repository.ContextBase context)
{
Seed(context);
}
protected override void Seed(Repository.ContextBase context)
{
// This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.
FrameworkDatabaseSeed.Seed(context);
}
}
Custom Project's DB Migrations Configuration:
public class Configuration : DbMigrationsConfiguration<Repository.Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
public void RunSeed(Repository.Context context)
{
Seed(context);
}
protected override void Seed(Repository.Context context)
{
// This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.
CustomDatabaseSeed.Seed(context);
}
}
The custom DbContext
//nothing special here, simply inherit ContextBase, IContext interface is purely for DI
public class Context : ContextBase, IContext
{
//Add the custom DBsets, i.e.
public DbSet<Chart> Charts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Assign the model configs, i.e.
modelBuilder.Configurations.Add(new ChartConfiguration());
}
}
Framework DbContext:
//again nothing special
public class ContextBase: DbContext
{
//example DbSet's
public virtual DbSet<Models.User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder);
}
In the global.asax AppStart:
//first remove the base context initialiser
Database.SetInitializer<ContextBase>(null);
//set the inherited context initializer
Database.SetInitializer(new MyMigrateDatabaseToLatestVersion());
In the web.config:
<connectionStrings>
<!--put the exact same connection string twice here and name it the same as the base and overridden context. That way they point to the same database. -->
<add name="Context" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
<add name="ContextBase" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
</connectionStrings>
(from the comments)
You're creating ContextBase objects directly, apparently as new T() in a generic method with ContextBase as a generic type argument, so any initialisers for ContextBase also run. To prevent creating ContextBase objects (if it should never be instantiated directly, if the derived context should always be used), you can mark the class as abstract.
Your ContextBase seems to have an initializer as well.. You can remove this by
Database.SetInitializer<ContextBase>(null);