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; }
}
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 have a model class:
public class Work
{
public long Id { get; set; }
[Required]
public string Name { get; set; }
}
I want this Work.Name will be unique, so I define the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext () : base() { }
public MyDbContext (DbContextOptions<MyDbContext > options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Work>(entity =>
entity.HasIndex(e => e.Name).IsUnique()
);
}
public DbSet<Work> Works { get; set; }
}
And I want to test this, so I have a test like this:
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work = new Work
{
Name = "Test Work"
};
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
Assert.Equal(1, context.Works.Count());
}
}
( The option object contains settings for InMemoryDatabase)
I don't really know what to check, but the test failed in the Assert, not in the second SaveChanges(). The database (the context) contains two objects with the same Name.
I went over all the relevant questions, but I did not see anyone answering what I was asking.
As others pointed out InMemory database provider ignore all possible constraints.
My suggestion would be then to use Sqlite provider with "in-memory' feature, which will throw an exception for duplicate unique keys.
public MyDbContext CreateSqliteContext()
{
var connectionString =
new SqliteConnectionStringBuilder { DataSource = ":memory:" }.ToString();
var connection = new SqliteConnection(connectionString);
var options = new DbContextOptionsBuilder<MyDbContext>().UseSqlite(connection);
return new MyDbContext(options);
}
private void Insert(Work work)
{
using (var context = CreateSqliteContext())
{
context.Works.Add(work);
context.SaveChanges();
}
}
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work1 = new Work { Name = "Test Work" };
var work2 = new Work { Name = "Test Work" };
Insert(work1);
Action saveDuplicate = () => Insert(work2);
saveDuplicate.Should().Throw<DbUpdateException>(); // Pass Ok
}
The test fails because the second SaveChanges() will throw an exception from the database that tells you that you cannot add another item because it already contains an object with that Name.
Unique constraints are not enforced silently. Instead, attempting to add a duplicate value will throw an exception when you try to do it. This is so that you can actually react to it, instead of only noticing it after the fact (when you see that the data you attempted to add is not there).
You can test that by using Assert.Throws:
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work = new Work
{
Name = "Test Work"
};
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
Assert.Throws<Exception>(() => context.SaveChanges());
}
}
You can also specify the exact exception there (I don’t remember on top of my head which exception it exactly is that is thrown there), and you can also assign it to a variable (Assert.Throws() returns the exception) and verify the exception message to make sure that this is the exact exception you expect.
I am using code first approach to connect with database and tables but due to some issue enable/add migration command is not creating my tables so I created tables manually. Th application build successfully that means I assume the objDbContext get my table. The name of Table is Task in database.
Below is my code
eDbContext objDbContext = new eDbContext ();
public List<TaskDetail> GetTasks(long eventId)
{
List<TaskDetail> listTask = new List<TaskDetail>();
try {
listTask = (from task in objDbContext.Tasks
where task.EventId==eventId
select new TaskDetail
{
Id = task.Id,
Title = task.Title,
Description = task.Description,
StartDate = task.StartDate,
EndDate = task.EndDate
}
).ToList();
}
catch(Exception ex) {
throw ex;
}
return listTask;
}
Below is database context
public class eDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
}
If you have similar problem (plural table names) for other entities, then you should remove PluralizingTableNameConvention (by default EF generates plural table names from entity type names). Add this code to your DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
If other tables have plural names, then you should just fix mapping for Task entity as #Valkyriee suggested.
Your DbContext Class should look like this:
public class eDbContext : DbContext
{
public IebContext()
: base("name=ConnectionStringName")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<eDbContext, Migrations.Configuration>("CatalogName"));
}
public DbSet<Task> Tasks{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TaskMap());
}
}
For your Migration you can create a new class like:
internal sealed class Configuration : DbMigrationsConfiguration<eDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
//know this might loss data while its true.
AutomaticMigrationDataLossAllowed = true;
ContextKey = "Path to your DbContext Class";
}
protected override void Seed(eDbContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
Now using this approach you can create your tables with EF code-first and change them later on. Note that I've added a Map Class for Tasks which means i am using fluent api for Mapping my entity:
public class TaskMap : EntityTypeConfiguration<Task>
{
public TaskMap ()
{
ToTable("Tasks");
HasKey(x => x.Id);
}
}
I have ready my model on code first EF and I try it on sql express and it works. But I have a problem translating it to a sql server: I don't have the permissions to recreate a database I can only add tables to an empty database.
I already see this answer but when I'm trying to replicate it I have some troubles with the context part:
public class DropCreateDatabaseTables : IDatabaseInitializer<Context> {
#region IDatabaseInitializer<Context> Members
public void InitializeDatabase(Context context)
I already put the reference to System.Data.Entity but that don't work and the Context class not is the referenced on System.Runtime.Remoting.Contexts
There is something wrong in the code? Or is a better solution with the last tools of EF?
EDIT:
Finally was:
DbContext:
public class PeopleContext: DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Adress> Adresses{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Add Entity type configuration classes
modelBuilder.Configurations.Add(new PersonConfiguration());
modelBuilder.Configurations.Add(new AdressConfiguration());
}
}
Initializer:
public class DropCreateDatabaseTables : IDatabaseInitializer<PeopleContext>
{
public void InitializeDatabase(PeopleContextContext)
{
bool dbExists;
using (new TransactionScope(TransactionScopeOption.Suppress))
{
dbExists = Context.Database.Exists();
}
if (dbExists)
{
// remove all tables
Context.Database.ExecuteSqlCommand("EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'");
Context.Database.ExecuteSqlCommand("EXEC sp_MSforeachtable #command1 = \"DROP TABLE ?\"");
// create all tables
var dbCreationScript = ((IObjectContextAdapter)Context).ObjectContext.CreateDatabaseScript();
Context.Database.ExecuteSqlCommand(dbCreationScript);
Context.SaveChanges();
}
else
{
throw new ApplicationException("No database instance");
}
}
}
Call:
class Program
{
static void Main(string[] args)
{
var person= new Person
{
Identifier= "John Doe"
};
Database.SetInitializer(new DropCreateDatabaseTables());
using (var context = new PeopleContext())
{
context.People.Add(person);
context.SaveChanges();
}
}
}
Thanks Lukas Kabrt!
The Context class in the example should be your DbContext class i.e. the class where you specify your DbSet<>s.
Example:
DbContext class
public class DataContext : DbContext {
public DbSet<Customer> Customers { get; set; }
}
DatabaseInitializer
public class DropCreateDatabaseTables : IDatabaseInitializer<DataContext> {
...
}
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.