I'm trying to fill the database with my StoreInitializer, but tables are empty, I'm using Entity Framework Core and ASP.NET Core, I want to configure my Initializer for using Update-Database in Package Manager Console. I followed this tutorial https://learn.microsoft.com/pl-pl/aspnet/core/data/ef-mvc/intro but on start or on updating, database is empty anyway. Visual studio shows me no errors.
StoreInitializer.cs :
namespace PC_SHOP.DAL
{
public static class StoreInitializer
{
public static void Initialize(StoreContext context)
{
context.Database.EnsureCreated();
if (context.Electronics.Any())
{
return;
}
// Działy główne
var electronics = new Electronic[]
{
new Electronic { ID=1, Name = "Laptopy"},
new Electronic { ID=2, Name = "Komputery"},
new Electronic { ID=3, Name = "Części PC"},
new Electronic { ID=4, Name = "Dla graczy"},
new Electronic { ID=5, Name = "Peryferia PC"},
new Electronic { ID=6, Name = "Sieci i komunikacja"},
new Electronic { ID=7, Name = "Oprogramowanie"},
};
foreach (Electronic e in electronics)
{
context.Electronics.Add(e);
}
context.SaveChanges();
// Rodzaje laptopów
var laptops = new List<Laptop>
{
new Laptop { ID=1, Name = "Laptopy", ElectronicID = 1, IconFileName = "1_laptop.jpg"},
new Laptop { ID=2, Name = "Laptopy Apple", ElectronicID = 1, IconFileName = "2_apple.jpg"},
};
foreach (Laptop l in laptops)
{
context.Laptops.Add(l);
}
context.SaveChanges();
}
}
}
StoreContext.cs :
namespace PC_SHOP.DAL
{
public class StoreContext : DbContext
{
public StoreContext(DbContextOptions<StoreContext> options) : base(options)
{
}
public DbSet<Electronic> Electronics { get; set; }
public DbSet<Laptop> Laptops { get; set; }
public DbSet<LaptopProduct> LaptopProducts { get; set; }
public DbSet<Employee> Employee { get; set; }
public DbSet<Project> Project { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Electronic>().ToTable("Electronic");
modelBuilder.Entity<Laptop>().ToTable("Laptop");
modelBuilder.Entity<LaptopProduct>().ToTable("LaptopProduct");
modelBuilder.Entity<Employee>().ToTable("Employee");
modelBuilder.Entity<Project>().ToTable("Project");
}
}
}
In ConfigurationServies in Startup.cs i added:
services.AddDbContext<StoreContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
and in appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Data Source=DESKTOP-J0OBBIO\\SQLEXPRESS;Initial Catalog=PC_SHOP;Integrated Security=SSPI;Database=Employee;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Ok i fixed that,
I added context parameter to Configure method in Startup.cs file. Everything else have not been changed.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, StoreContext context )
{
...
//After app.UseMvc()
StoreInitializer.Initialize(context);
}
And it's filling my database correctly
Add this line to Program.cs Main() method :
StoreInitializer.Initialize(context);
public static void Main(string[] args)
{
//CreateWebHostBuilder(args).Build().Run();
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<AppDbContext>();
//context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
EnsureCreated can be used to create the database and tables in prototyping and testing scenarios. It should be used instead of, not together with Migrations. It will only create the database if it does not exist or contains no tables. In other cases it will do nothing.
Note that if the database exists it will not do anything, so you need to use a database name that does not exist.
Really you should be generating migrations from the comand line then running the migrations from your initializer instead of using .EnsureCreated. EnsureCreated is for in memory database that is created each time. ie prototyping.
Related
I am working on a .NET 6 Winforms project that uses Entity Framework Core 6.I can retrieve data OK, but when I go to Edit data and call SaveChanges on the DbContext, it just hangs with no error. The UI freezes and when debugging, the code never steps past the SaveChanges call.
The data is actually updated in the database but it never moves past this point and I have to abort the application.
I enabled logging and I can see the SQL calls being made to EF Core 6 in the log with no errors.
I am wondering if there is some threading issue I am missing where it is not returning back to the UI, but then I would expect the debugger to at least be able to step past the SaveChanges call, but it does not. I have searched the Internet and cannot find any solution.
Here is the DbContext class;
public class MainDbContext : DbContext
{
public MainDbContext(DbContextOptions<MainDbContext> options)
: base(options)
{
}
public DbSet<User> Users => Set<User>();
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return result;
}
public override int SaveChanges()
{
return SaveChangesAsync().GetAwaiter().GetResult();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new UserConfiguration());
}
}
Here is the User Entity class;
public class User
{
public int Id { get; set; }
public string? CompanyName { get; set; } = string.Empty;
public string? Name { get; set; } = string.Empty;
public bool? Active { get; set; }
}
I am using DI to handle the DbContext. Here is the AddDbContext in Program.cs
Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, config) =>
{
// Configure the app here
})
.ConfigureServices((hostContext, services) =>
{
var sqlTransientErrors = new List<int>() { 10928, 10929, 10053, 10054, 10060, 40197, 40540, 40613, 40143, 64 };
var MainDbConnectionString = "{CONNECTION STRING OMITTED}";
var commandTimeout = "30";
services.AddTransient<UserTest>();
services.AddDbContext<CdiMainDbContext>(options =>
options.UseSqlServer(
MainDbConnectionString,
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), sqlTransientErrors);
sqlOptions.CommandTimeout(Convert.ToInt32(commandTimeout));
}));
})
.UseSerilog();
Here is the Click event code for the Update button on the form;
private void UpdateButton_Click(object sender, EventArgs e)
{
int userId = int.Parse(UserIdTextBox.Text.Trim());
var user = _mainDbContext.Users.AsNoTracking().FirstOrDefault(x => x.Id == userId);
if (user != null)
{
user.Name = NameTextBox.Text;
user.CompanyName = CompanyNameTextBox.Text;
user.Active = ActiveCheckBox.Checked;
_mainDbContext.Users.Update(user);
_mainDbContext.SaveChanges();
}
}
Any ideas?
I'm writing integration test with ef core using sqlite memory database. Here is the code:
public async Task GetCustomerAndRidesById_When_MultipleCustomersArePresent()
{
var connectionStringBuilder =
new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connection = new SqliteConnection(connectionStringBuilder.ToString());
var options = new DbContextOptionsBuilder<TravelContext>()
.UseSqlite(connection)
.Options;
var customer = _customerBuilder.WithDefaults();
Customer customerFromRepo;
using (var context = new TravelContext(options))
{
context.Database.OpenConnection();
context.Database.EnsureCreated();
await context.Customers.AddAsync(customer);
await context.SaveChangesAsync();
_output.WriteLine($"Customer ID: {customer.Id}");
//var customerRepository = new CustomerRepository(context);
//customerFromRepo = await customerRepository.GetByIdWithRidesAsync(customer.Id);
}
using (var context = new TravelContext(options))
{
var customerRepository = new CustomerRepository(context);
try
{
customerFromRepo = await customerRepository.GetByIdWithRidesAsync(customer.Id);
}
catch (System.Exception e)
{
throw;
}
}
customerFromRepo.Should().BeEquivalentTo(customer);
customerFromRepo.Rides.Should().HaveCount(customer.Rides.Count);
}
The above code throws following error
Collection is read-only
error. However if I comment out the second using block and uncomment the lines inside first using block, records are retrieved and test passes.
Here is my Customer class:
public class Customer : BaseEntity<Guid>, IAggregateRoot
{
private Customer()
{
// required by EF
}
public Customer(string name, List<Ride> rides)
{
Name = name;
_rides = rides;
}
public string Name { get; set; }
private readonly List<Ride> _rides = new List<Ride>();
public IReadOnlyCollection<Ride> Rides => _rides.AsReadOnly();
}
I'm puzzled. Can anyone explain why?
Thanks
Enabling the below configuration fixes the issue.
var navigation = builder.Metadata.FindNavigation(nameof(Customer.Rides));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
Credits and Thanks to Ivan Stoev for guidance.
I have basically followed the whole tutorial from scratch, besides skipping javascript client. However, i copied some code from the sample Asp.Net Identity + EF Core combined and database with all tables was created successfully. However, when i run the identity server, there is no seed. I debugged the program.cs and the seed var is always false making it skipping the seed condition.
public class Program
{
public static void Main(string[] args)
{
var seed = args.Any(x => x == "/seed");
if (seed) args = args.Except(new[] { "/seed" }).ToArray();
var host = CreateWebHostBuilder(args).Build();
if (seed)
{
using (var scope = host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
SeedData.EnsureSeedData(scope.ServiceProvider);
return;
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
this is the seed data class just like in the sample
public class SeedData
{
public static void EnsureSeedData(IServiceProvider provider)
{
provider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
provider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
provider.GetRequiredService<ConfigurationDbContext>().Database.Migrate();
{
var userMgr = provider.GetRequiredService<UserManager<ApplicationUser>>();
var alice = userMgr.FindByNameAsync("alice").Result;
if (alice== null)
{
alice = new ApplicationUser
{
UserName = "alice"
};
var result = userMgr.CreateAsync(alice, "Pass123$").Result
if (!result.Succeeded)
{
throw new Exception(result.Errors.First().Description);
}
alice = userMgr.FindByNameAsync("alice").Result;
result = userMgr.AddClaimsAsync(user, new Claim[]{
new Claim(JwtClaimTypes.Subject, "1"),
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith#email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.Role, "Admin")
}).Result;
if (!result.Succeeded)
{
throw new Exception(result.Errors.First().Description);
}
Console.WriteLine("user created");
}
else
{
Console.WriteLine("user already exists");
}
}
{
var context = provider.GetRequiredService<ConfigurationDbContext>();
if (!context.Clients.Any())
{
foreach (var client in Config.GetClients())
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if (!context.IdentityResources.Any())
{
foreach (var resource in Config.GetIdentityResources())
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if (!context.ApiResources.Any())
{
foreach (var resource in Config.GetApis())
{
context.ApiResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
}
}
what am i missing? Everything code wise is the same, startup class too etc...
For passing string[] args, you need to launch project by Project.exe instead of IIS Express.
Follow Project Properties->Debug->Application Arguments->/seed->Launch Project from ProjectName.
In general, you may consider deciding seeding data by check wether there is any data in database instead of from command arguments.
Is it OK to seed data in the startup.cs? Are there any advantages, disadvantages to doing it there rather than in the DbContext or Migration Configuration Class?
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
SeedData();
}
public void SeedData()
{
using (ApplicationDbContext _db = new ApplicationDbContext()) {
if (!_db.Books.Any(n => n.Name == "Seeded in Startup.cs"))
{
_db.Books.Add(new Book() { Name = "Seeded in Startup.cs", Date = "1966" });
}
_db.SaveChanges();
}
}
We created a SeedHelpers.cs
In this file all seed data are available.
public async Task Seed()
{
try
{
if (RoleManager.Roles.ToList().Count == 0)
foreach (string role in typeof(RoleConst).GetConstValue<string>())
await RoleManager.CreateAsync(new RoleEntity { Name = role.ToString() });
if (UserManager.Users.ToList().Count == 0)
{
UserEntity entity = new UserEntity
{
Email = "y",
Active = true,
Deleted = false,
EmailConfirmed = true,
Created = DateTime.UtcNow,
Modified = DateTime.UtcNow,
Name = "y",
UserName = "x"
};
await UserManager.CreateAsync(entity, "fg#123");
await UserManager.AddToRoleAsync(entity, RoleConst.Admin);
//Send Invitation email to Admin in the Production.
}
if(DapperLanguage.All().ToList().Count()==0)
{
await DapperLanguage.AddAsync(new Language
{
Id = Guid.NewGuid(),
Code = LanguageConst.English,
Name = "English"
});
await DapperLanguage.AddAsync(new Language
{
Id = Guid.NewGuid(),
Code = LanguageConst.Arabic,
Name = "Arabic"
});
}
}
catch (Exception ex)
{
LogManager.LogError(JsonConvert.SerializeObject(new { class_name = this.GetType().Name, exception = ex }));
}
}
And In Startup.cs file
public void Configuration(IAppBuilder app, Helpers.SeedHelpers seed)
{
ConfigureAuth(app);
seed.Seed().Wait();
}
Try this type of method.
If you are using ef core you can now use HasData() in the ef mapping configurations:
https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding
If using ef 6 you can set an initialiser on the DbContext and maybe store the seed data for each entity in a separate file in a separate project?
http://www.entityframeworktutorial.net/code-first/seed-database-in-code-first.aspx
I'm trying to understand how to dynamically create the connection string for my DbContext, but my application says it has no connection string in the app.config (and that's correct because I don't want to use it in the app.config or web.config). This is what I have:
In my solution I have a project called InterfaceApp. It is a ASP.NET MVC 5 application. When I put my connection string in the web.config all seems to be working fine.
In my solution I have an other project called InterfaceApp.Connector.Erp1. Here I want to connect to an ERP application and fetch some items. So in my repository I have:
namespace InterfaceApp.Connector.Erp1.Repository
{
internal class ItemRepository : IItemRepository
{
public IEnumerable<Item> Items
{
get
{
List<Item> items = new List<Item>();
using (Models.Entities context = new Models.Entities())
{
var itemList = context.Items.ToList();
foreach(var item in itemList)
{
items.Add(new Item() { Id = item.ID, Description = item.Description, ItemCode = item.ItemCode });
}
}
return items.ToList();
}
}
}
}
I've created a partial class to connect to the database:
namespace InterfaceApp.Connector.Erp1.Models
{
public partial class Entities
{
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
}
}
The Context class that is auto-generated by EF6 (Db First) looks like this:
namespace InterfaceApp.Connector.Erp1.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
}
When I run my application, the debugger stops at the auto-generated class, but not at my partial class. Because it cannot find the connection string Entities in my app.config and web.config it generates an error saying that the connection string is not found in the application config file. What am I doing wrong?
When you are calling the DbContext, you're calling the empty constructor (new Models.Entities()). Thus, it will call the auto-generated DbContext. If you want to call your partial class, you need to call it explicitly with the parameter.
Remember when you create a partial class, the compiler merges them, so you have this when compiled :
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
What you probably need a a method to create your DbContext and call it instead of calling a new DbContext.
public static Entities Create()
{
return new Entities(ConnectionString());
}
Then you can use it this way :
using (var context = Entities.Create())
{
//...
}