I'm learning about Entity Framework and I am currently facing a problem where I take about 10 seconds to retrieve data from a database or to update a row, as if my code were actually stuck for a period of time, even though debugging it everything went normal.
The code itself, actually works as expected, besides this delay.
Searching on Google and here I did not found other people with this issue related to Entity Framework.
I think that maybe it's something to do with my CodeFirstMySQLEntities class constructor, but not sure.
If someone could provide me with a guidance, I would appreciate.
This is the main code:
namespace CodeFirstMySQL
{
class Program
{
static void Main(string[] args)
{
UserRepository userRepository = new UserRepository();
userRepository.Update("Klein", "OtherName");
//delay experienced here
Console.WriteLine("done");
Console.ReadLine();
}
}
}
This is the DbContext code:
namespace CodeFirstMySQL.Database
{
public class CodeFirstMySQLEntities : DbContext
{
public CodeFirstMySQLEntities() : base("CodeFirstMySQLEntities") { }
public DbSet<UserModel> Users { get; set; }
}
}
This is the UserModel code:
namespace CodeFirstMySQL.Database.Models
{
public class UserModel
{
[Key, StringLength(100)]
public string firstName { get; set; }
[StringLength(100)]
public string lastName { get; set; }
}
}
This is the repository code:
namespace CodeFirstMySQL.Database.Repositories
{
public class UserRepository
{
public void Insert(UserModel user)
{
using (var context = new CodeFirstMySQLEntities())
{
context.Users.Add(user);
context.SaveChanges();
}
}
public void Delete(string firstName)
{
using (var context = new CodeFirstMySQLEntities())
{
UserModel user = context.Users.FirstOrDefault(x => x.firstName == firstName);
context.Users.Remove(user);
context.SaveChanges();
}
}
public void Update(string lastNameOld, string lastNameNew)
{
using (var context = new CodeFirstMySQLEntities())
{
UserModel user = context.Users.FirstOrDefault(x => x.lastName == lastNameOld);
user.lastName = lastNameNew;
context.SaveChanges();
}
}
public IList<UserModel> GetUsers()
{
using (var context = new CodeFirstMySQLEntities())
{
return context.Set<UserModel>().ToList();
}
}
}
}
Connection String:
<connectionStrings>
<add name="CodeFirstMySQLEntities" connectionString="Server=localhost; Database=CodeFirst; Uid=root; Pwd=" providerName="MySql.Data.MySqlClient"/>
</connectionStrings>
The delay is almost certainly due to the time Entity Framework takes to fire up. You can confirm this by trying a second update before exiting your code.
The following excerpt explains what is going on
Model Caching
There is some cost involved in discovering the model, processing Data Annotations and applying fluent API configuration. To avoid incurring this cost every time a derived DbContext is instantiated the model is cached during the first initialization. The cached model is then re-used each time the same derived context is constructed in the same AppDomain.
Related
We're using EF core and I just learned that using the DbContext as long lived object is not a good idea. The DbContext is not designed to be a long lived object.
So I now inject a new instance for every transaction, which I thought worked great. But now I see that child entities are not being populated after disposing the DbContext and creating a new one.
Some example code to show the problem
public InitialTestData(Func<IMachineRepository> machineRepositoryFactory)
{
{
using var machineRepository = machineRepositoryFactory();
if (!machineRepository.FindAll().Any())
{
machineRepository.Add("411-01", "https://localhost:5002/411-01");
machineRepository.Add("411-02", "https://localhost:5002/411-02");
machineRepository.Add("411-03", "https://localhost:5002/411-03");
machineRepository.Add("413-01", "https://localhost:5002/413-01");
machineRepository.Add("413-02", "https://localhost:5002/413-02");
machineRepository.Add("413-03", "https://localhost:5002/413-03");
machineRepository.Save();
foreach (var machineEntity in machineRepository.FindAll())
{
var machineStatusChangeEntity = new MachineStatusChange
{
DateTime = DateTime.Now,
State = MachineStateDataModel.Idle
};
machineEntity.StatusChanges.Add(machineStatusChangeEntity);
machineRepository.Save();
}
}
var bla = machineRepository.FindAll().ToList();
foreach (var machine in bla)
{
// machine entity has a list of one child entity as expected
}
}
{
using var machineRepository = machineRepositoryFactory();
var bla = machineRepository.FindAll().ToList();
foreach (var machine in bla)
{
// machine entity has zero child entities, why? it's present in the database
}
}
}
In the above example I (autofac) creates the machine repository twice. Each repository gets a DbContext injected. The first time, I add some entities and save the changes (persisted in database successfully). I can (obviously) still query on this repository, as everything is still cached as well.
But when I then recreate the machine repository for the second time, it is able to query the Machine entities (the "parent" entities) but it no longer is able to find the related child elements. As mentioned, they are stored in the database, I double checked the Guids of parent and childs, the all match perfectly. Yet EF code doesn't "remake" the relation somehow.
Am I missing something important in the OnModelConfiguring (posted below) or did I design the entities wrong?
See below the remaining classes that make the "database layer"
Repository pattern:
public class MachineRepository : RepositoryBase<Machine>, IMachineRepository
{
public MachineRepository(PatDatabase repositoryContext) : base(repositoryContext)
{
}
}
public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
protected PatDatabase RepositoryContext { get; set; }
protected RepositoryBase(PatDatabase repositoryContext)
{
RepositoryContext = repositoryContext;
}
public IQueryable<T> FindAll() => RepositoryContext.Set<T>();
public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression) =>
RepositoryContext.Set<T>().Where(expression);
public EntityEntry<T> Refresh(T entity) => RepositoryContext.Entry(entity);
public void Create(T entity) => RepositoryContext.Set<T>().Add(entity);
public void Update(T entity) => RepositoryContext.Set<T>().Update(entity);
public void Delete(T entity) => RepositoryContext.Set<T>().Remove(entity);
}
The two entity classes
[Table("Machine")]
public class Machine
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string BaseUrl { get; set; }
public ICollection<MachineStatusChange> StatusChanges { get; set; } = new List<MachineStatusChange>();
}
[Table("MachineStatusChange")]
public record MachineStatusChange
{
[Key]
public Guid Id { get; set; }
[ForeignKey(nameof(Machine))]
public Guid MachineId { get; set; }
public DateTime DateTime { get; set; }
public MachineStateDataModel State { get; set; }
public Machine Machine { get; set; }
}
The "PatDatabase" / DbContext
public class PatDatabase : DbContext
{
private readonly string _connectionString;
// TODO, discuss/investigate
// Any purpose to define DbSet<T> as they did in tutorial?
//public DbSet<Machine> Machines { get; set; }
/// <summary>
/// Used by dotnet ef migrations
/// NOTE: Autofac will always prefer the constructor with most arguments
/// </summary>
public PatDatabase()
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream("IAI.ToolSuite.PAT.Server.appsettings.json");
var configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();
// TODO BP: replace "LocalMySqlConnection" with "MySqlConnection" when figured out migrations in docker properly
// It seems that 'dotnet ef migrations remove' requires access to the database(??)
// In my case, my docker-compose is down, I would expect that's not a problem.... To be investigated...
_connectionString = configuration["MySqlConnection:connectionString"];
}
/// <summary>
/// Constructor used by autofac (autofac will always prefer constructor with most arguments if resolvable)
/// </summary>
public PatDatabase(IConfiguration configuration, IWebHostEnvironment env)
{
if (env.IsLocal())
{
_connectionString = configuration["MySqlConnection:connectionString"];
}
else
{
_connectionString = configuration["MySqlConnection:connectionString"];
}
}
/// <summary>
/// IMPORTANT
/// override the OnConfiguring allows us to keep a parameterless constructor
/// ef migrations tool requires a parameterless constructor
/// </summary>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(_connectionString, MySqlServerVersion.LatestSupportedServerVersion,
builder =>
{
builder.EnableRetryOnFailure(20, TimeSpan.FromSeconds(10), null);
});
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Machine>().HasMany(m => m.StatusChanges);
modelBuilder.Entity<MachineStatusChange>()
.HasOne(m => m.Machine)
.WithMany(m => m.StatusChanges);
}
public void MigrateIfNeeded()
{
if (Database.GetPendingMigrations().Any())
{
Database.Migrate();
}
// I think calling ensure created before migrations can cause issues
// when migrations exist but the database or tables were dropped
Database.EnsureCreated();
}
}
Argh... I wasn't including the child entities at all after disposing it...
My apologies for wasting anybodies time.
The obvious fix
var bla = machineRepository.FindAll().Include(m => m.StatusChanges).ToList();
My actual problem which I had was this
using var machineRepository = _machineRepositoryFactory();
var machines = machineRepository
.FindAll();
// Here, it would've had been a GREAT idea to
// capture the resulting IQueryable return by `Include`
machines
.Include(m => m.StatusChanges)
// Iterating over machines now, will obviously not include the child entities.
So the fix...
machine = machines
.Include(m => m.StatusChanges)
Or even simpler of course...
using var machineRepository = _machineRepositoryFactory();
var machines = machineRepository
.FindAll()
.Include(m => m.StatusChanges);
I'm working on a Xamarin mobile app using .NET Framework, and SQLite.NET-PCL. My XAML Form uses MVVM architecture, on my main form I am attempting to display all movies in a ListView, first I made sure the view itself works correctly, and now I am attempting to connect it to my DB service. When the DB service initializes and attempts to create any table using CreateTableAsyc() the program gets stuck in that function even after appearing to succeed.
Initially, I thought it was due to using created classes as properties, which SQLite does not support, so I corrected that. Next, I checked if it was due to not using Hardware Acceleration so I enabled it. I then attempted to debug using ContinueWith() and printed the result to the debug console, it says 'Migrated' but never exits the function. Xamarin XAML Hot Reload then times out. What could be causing this?
Example of one of the Types:
using SQLite;
public class Movie
{
public Movie() {}
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Title { get; set; }
public DateTime Length { get; set; }
}
DBService
using Xamarin.Essentials;
public static class DBService
{
private static SQLiteAsyncConnection dbConn;
public static async Task Init()
{
if (dbConn != null)
{
return;
}
string databasePath = Path.Combine(FileSystem.AppDataDirectory, "Movies.db");
dbConn = new SQLiteAsyncConnection(databasePath, false);
await dbConn.CreateTableAsync<Movie>().ContinueWith(results =>
{
Debug.WriteLine(results.Result.ToString()); // This prints migrated
});
Debug.WriteLine("After table created"); // This never prints
}
public static async Task<IEnumerable<Movie>> GetMovies()
{
await Init();
return await dbConn.Table<Movie>().ToListAsync();
}
}
MovieViewModel (View Model for main view)
public class MovieViewModel
{
public List<Movie> Movies { get; set; }
public MovieViewModel ()
{
Movies = (List<Movie>)DBService.GetMovies().Result;
}
}
Wrap awaitable in something that runs after constructor returns:
MainThread.BeginInvoke(async () => Movies = (await DBService.GetMovies()).ToList() );
Optional:
GetMovies could return a List, to match Movies declaration. Then would not need .ToList(), or (List<Movie>) cast:
public static async Task<List<Movie>> GetMovies() { ... }
MainThread.BeginInvoke(async () => Movies = await DBService.GetMovies() );
We do have an entity class defined as below:
[Table("Users", Schema = "Mstr")]
[Audited]
public class User
{
public virtual string FamilyName { get; set; }
public virtual string SurName { get; set; }
[NotMapped]
public virtual string DisplayName
{
get => SurName + " " + FamilyName;
private set { }
}
}
This is working just fine. Now we would like to extract the logic part SurName + " " + FamilyName to a helper class which is usually injected with dependency injection. Unfortunately DI is not working for an entity class.
Therefor my question: is there any way to intercept the creation of new User objects? Is there a method from EF which I could override to execute some additional logic after a User object was created by EF?
Actually (at least in EF Core 6) you can use DI when constructing entities. Solution is a little bit hacky and based on the EF Core capability to inject "native" services like the context itself into entities constructors:
Currently, only services known by EF Core can be injected. Support for injecting application services is being considered for a future release.
And AccessorExtensions.GetService<TService> extension method which seems to support resolving services from DI.
So basically just introduce ctor accepting your DbContext as a parameter to the entity and call GetService on it and use service:
public class MyEntity
{
public MyEntity()
{
}
public MyEntity(SomeContext context)
{
var valueProvider = context.GetService<IValueProvider>();
NotMapped = valueProvider.GetValue();
}
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
// Example value provider:
public interface IValueProvider
{
string GetValue();
}
class ValueProvider : IValueProvider
{
public string GetValue() => "From DI";
}
Example context:
public class SomeContext : DbContext
{
public SomeContext(DbContextOptions<SomeContext> options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
And example:
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IValueProvider, ValueProvider>();
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db"));
var serviceProvider = serviceCollection.BuildServiceProvider();
// init db and add one item
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
someContext.Database.EnsureDeleted();
someContext.Database.EnsureCreated();
someContext.Add(new MyEntity());
someContext.SaveChanges();
}
// check that value provider is used
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
var myEntities = someContext.Entities.ToList();
Console.WriteLine(myEntities.First().NotMapped); // prints "From DI"
}
Note that var valueProvider = context.GetService<IValueProvider>(); will throw if service is not registered so possibly next implementation is better:
public MyEntity(SomeContext context)
{
var serviceProvider = context.GetService<IServiceProvider>();
var valueProvider = serviceProvider.GetService<IValueProvider>();
NotMapped = valueProvider?.GetValue() ?? "No Provider";
}
Also you can consider removing not mapped property and creating separate model with it and service which will perform the mapping.
Also in 7th version of EF Core a new hook for exactly this case should be added. See this github issue.
UPD. EF Core 7 approach.
EF 7 adds IMaterializationInterceptor (and bunch of others - see the docs) which can be used exactly for this goal. So updated code can look like the following:
No need for ctor accepting context in entity:
public class MyEntity
{
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
Create an interceptor and overload one of it's methods (I went with InitializedInstance):
class NotMappedValueGeneratingInterceptor : IMaterializationInterceptor
{
public static NotMappedValueGeneratingInterceptor Instance = new ();
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (entity is MyEntity my)
{
var valueProvider = materializationData.Context.GetService<IValueProvider>();
my.NotMapped = valueProvider.GetValue();
}
return entity;
}
}
And add interceptor to the context setup, with our DI approach AddDbContext changes to:
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db")
.AddInterceptors(NotMappedValueGeneratingInterceptor.Instance));
In your DbContext or whatever your context file is called you can intercept the SaveChanges() method and override it with your own things. In my example I override SaveChanges() to automatically add my audit fields so I don't have to duplicate it all over the code in a million places.
here is my example. So when a new object is being created you can override it. In My example I override both New records Added and Records modified.
These are notated at EntitState.Added and EntityStateModified.
Here is the code.
public override int SaveChanges()
{
var state = this.ChangeTracker.Entries().Select(x => x.State).ToList();
state.ForEach(x => {
if (x == EntityState.Added)
{
//Create new record changes
var created = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Added).Select(e => e.Entity).ToArray();
foreach (var entity in created)
{
if (entity is AuditFields)
{
var auditFields = entity as AuditFields;
auditFields.CreateDateTimeUtc = DateTime.UtcNow;
auditFields.ModifiedDateTimeUtc = DateTime.UtcNow;
auditFields.Active = true;
}
}
}
else if (x == EntityState.Modified)
{
//Modified record changes
var modified = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified).Select(e => e.Entity).ToArray();
foreach (var entity in modified)
{
if (entity is AuditFields)
{
var auditFields = entity as AuditFields;
auditFields.ModifiedDateTimeUtc = DateTime.UtcNow;
}
}
}
else
{
//do nothing
}
});
return base.SaveChanges();
}
Since you said:
is there any way to intercept the creation of new User objects?
You would want to do your logic in the EntityState.Added area of code above and this will allow you to intercept the creation of your new User and do whatever you want to do before it is saved to Database.
I am using a shared database fixture for my tests, but when running multiple tests at the same time, I get the following error message:
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
This is my code of my Fixture:
public class SharedDatabaseFixture : IDisposable
{
public static readonly object _lock = new object();
private static bool _databaseInitialized;
private const string postgresConnectionString = "Host=localhost;Database=IntegrationTests; Username=postgres;Password=password";
public SharedDatabaseFixture()
{
Connection = new NpgsqlConnection(postgresConnectionString);
Seed();
Connection.Open();
}
public DbConnection Connection { get; }
public AppDbContext CreateContext(DbTransaction transaction = null!)
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkNpgsql()
.AddMediatR(typeof(IAggregateRoot).Assembly)
.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>))
.AddDbContext<AppDbContext>(options => options.UseNpgsql(Connection))
.BuildServiceProvider();
ServiceLocator.SetLocatorProvider(serviceProvider);
DomainEvents.Mediator = () => ServiceLocator.Current.GetInstance<IMediator>();
var builder = new DbContextOptionsBuilder<AppDbContext>();
builder.UseNpgsql(Connection).UseInternalServiceProvider(serviceProvider);
var context = new AppDbContext(builder.Options);
if (transaction != null)
{
context.Database.UseTransaction(transaction);
}
return context;
}
private void Seed()
{
lock (_lock)
{
if (!_databaseInitialized)
{
using (var context = CreateContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var appDbContextSeed = new AppDbContextSeed(context);
appDbContextSeed.SeedAsync().Wait();
}
_databaseInitialized = true;
}
}
}
public void Dispose() => Connection.Dispose();
}
The code I am testing uses events and those events do queries to the database. Therefore, I am registering some services and also a DbContext.
The problem is, when I run multiple tests at the same time, events are raised at the same time as well and because they are all using the same DbContext, it throws an exception when two handlers try to use the DbContext at the same time.
So, my question is: how can I instantiate a DbContext for each test (but using the same connection) or prevent it from using the DbContext at the same time?
An Example of one of my tests:
public class Project_Create : IClassFixture<SharedDatabaseFixture>
{
public SharedDatabaseFixture Fixture { get; }
public Project_Create(SharedDatabaseFixture fixture) => Fixture = fixture;
[Fact]
public void Creates_succesfully()
{
var project = new Project(SeedConstants.TEST_COMPANY_ID, "ABC", "Hallo123", "2018-123");
Assert.Equal(SeedConstants.TEST_COMPANY_ID, project.CompanyId);
Assert.Equal("ABC", project.Code);
Assert.Equal("Hallo123", project.Description);
Assert.Equal("2018-123", project.Number);
}
}
Project.cs:
public class Project : BaseEntity<Guid, ProjectValidator, Project>, IAggregateRoot
{
public Guid CompanyId { get; private set; }
public string Code { get; private set; }
public string Description { get; private set; }
public string Number { get; private set; }
public Project(Guid companyId, string code, string description, string number)
{
CompanyId = companyId;
Code = code;
Description = description;
Number = number;
Validate(this);
DomainEvents.Raise(new SetCompanyIdEvent(companyId)).GetAwaiter().GetResult();
}
}
As you can see, this project class raises an event. This event has a handler and looks like this:
public class CheckIfProjectIdExistsHandler : INotificationHandler<SetProjectIdEvent>
{
private readonly IAsyncRepository<Project> _projectRepository;
public CheckIfProjectIdExistsHandler(IAsyncRepository<Project> projectRepository)
{
_projectRepository = projectRepository;
}
public async Task Handle(SetProjectIdEvent notification, CancellationToken cancellationToken)
{
var project = await _projectRepository.GetByIdAsync(notification.ProjectId, cancellationToken);
if (project == null)
{
throw new ProjectDoesNotExistsException($"The project with ID {notification.ProjectId} does not exist.");
}
}
}
I hope this illustrates what I am testing
The answer is always simpler than you think.
When adding the DbContext in the Service Provider, I didn't specify the ServiceLifetime, so it is a singleton by default. Changing this to Transient solves the issue. Then the Connection should also be changed by the connectionString, so there are no multiple operations on the same connection.
So, this line:
.AddDbContext<AppDbContext>(options => options.UseNpgsql(Connection))
Should be change like so:
.AddDbContext<AppDbContext>(options => options.UseNpgsql(postgresConnectionString), ServiceLifetime.Transient)
Also, The registration of the repository should be as Transient and not Scoped.
The original problem is to release current db file, delete it and create new one, so database could be completely fresh and clean. Why do I get exception if (as i believe) file should be released when context is disposed?
UPDATE:
Indeed, GC solves the problem.
Now I get another problem which is not obvious to me either. I just disposed context, created new one and tables should've been lazily initialized. server.db file is created but has no tables. Can someone explain please?
class Program
{
static void Main(string[] args)
{
var dbFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "server.db");
if (File.Exists(dbFilename))
File.Delete(dbFilename);
using (var ctx = new CustomContext())
{
ctx.Products.Add(new Product {Cost = 10});
ctx.SaveChanges();
}
// SOLUTION (1)
GC.Collect();
GC.WaitForPendingFinalizers();
if (File.Exists(dbFilename))
File.Delete(dbFilename); // (1) WinIOError : can't access file server.db
using (var ctx = new CustomContext())
{
ctx.Products.Add(new Product {Cost = 10});
ctx.SaveChanges(); // (2) no such table: Products
}
}
}
public class CustomContext : DbContext
{
public DbSet<Product> Products { get; set; }
public CustomContext() : base("ServerConnection")
{
}
// App.config contains:
// <connectionStrings>
// <add name="ServerConnection" connectionString="Data Source=|DataDirectory|\server.db" providerName="System.Data.SQLite" />
// </connectionStrings>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var dbInitializer = new SqliteCreateDatabaseIfNotExists<CustomContext>(modelBuilder);
Database.SetInitializer(dbInitializer);
}
}
public class Product
{
[Key]
public double Cost { get; set; }
}