Here is my model classes:
public abstract class BaseEntity
{
[Key]
public Guid Guid { get; set; }
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id
{
get;
set;
}
}
public class Menu : Abstract.BaseEntity
{
public string Title { get; set; }
public string Url { get; set; }
}
public class SpaceShipEntities : DbContext
{
public DbSet<Menu> Menu { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
I use migrations. And there is my configuration class:
internal sealed class Configuration : DbMigrationsConfiguration<SpaceProject.Models.SpaceShipEntities>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SpaceProject.Models.SpaceShipEntities context)
{
context.Menu.AddOrUpdate(
c => c.Title,
new Menu() { Guid = Guid.NewGuid(), Title = "Главная" }
);
}
}
When i use package command "Update-Database -Verbose" in empty database new rows has inserted to database. But when i use "Update-Database -Verbose" second time, and Seed method tries to update rows, i have following error:
When updating records the error occurred. For details, see the inner exception. ---> System.InvalidOperationException: Changing column template "Identity" is not supported. Column: "Id". Table: "CodeFirstDatabaseSchema.Menu".
The Guid will be indexed anyway if you use it as a key. I would use Id as the key and decorate Guid with [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)] then seed it with:
protected override void Seed(SpaceProject.Models.SpaceShipEntities context)
{
context.Menu.AddOrUpdate(
c => c.Guid,
new Menu() { Guid = Guid.Parse("..."), Title = "Главная" }
);
}
Related
I have the following DbContext in a .net5 winforms EF XAF 21.2.5 standard security application created with the wizard and it runs OK.
[TypesInfoInitializer(typeof(DXApplication42ContextInitializer))]
public class DXApplication42EFCoreDbContext : DbContext {
public DXApplication42EFCoreDbContext(DbContextOptions<DXApplication42EFCoreDbContext> options) : base(options) {
}
public DbSet<ModuleInfo> ModulesInfo { get; set; }
public DbSet<ModelDifference> ModelDifferences { get; set; }
public DbSet<ModelDifferenceAspect> ModelDifferenceAspects { get; set; }
public DbSet<PermissionPolicyRole> Roles { get; set; }
public DbSet<DXApplication42.Module.BusinessObjects.ApplicationUser> Users { get; set; }
public DbSet<DXApplication42.Module.BusinessObjects.ApplicationUserLoginInfo> UserLoginInfos { get; set; }
public DbSet<ReportDataV2> ReportDataV2 { get; set; }
//public DbSet<DtoNum> TempNums { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<DXApplication42.Module.BusinessObjects.ApplicationUserLoginInfo>(b => {
b.HasIndex(nameof(DevExpress.ExpressApp.Security.ISecurityUserLoginInfo.LoginProviderName), nameof(DevExpress.ExpressApp.Security.ISecurityUserLoginInfo.ProviderUserKey)).IsUnique();
});
//modelBuilder.Entity<DtoNum>().HasNoKey();
}
}
public class DtoNum
{
public int Id { get; set; }
}
However if I uncomment the 2 commented lines I get an error
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=DevExpress.ExpressApp.EFCore.v21.2
StackTrace:
at DevExpress.ExpressApp.EFCore.EFCoreTypeInfoSource.SetupKeyMembers(TypeInfo typeInfo)
at DevExpress.ExpressApp.EFCore.EFCoreTypeInfoSource.RegisterEntity(Type type)
at DevExpress.ExpressApp.EFCore.TypeInfoSourceHelper.InitTypeInfoSource(EFCoreObjectSpaceProvider objectSpaceProvider)
at DevExpress.ExpressApp.EFCore.EFCoreObjectSpaceProvider..ctor(IDbContextFactory`1 dbContextFactory, ITypesInfo typesInfo)
at DevExpress.EntityFrameworkCore.Security.SecuredEFCoreObjectSpaceProvider..ctor(ISelectDataSecurityProvider selectDataSecurityProvider, IDbContextFactory`1 dbContextFactory, ITypesInfo typesInfo)
at DXApplication42.Win.DXApplication42WindowsFormsApplication.CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) in C:\Users\kirst\source\repos\DXApplication42\DXApplication42.Win\WinApplication.cs:line 34
Shown as
Looking inside EFCoreTypeInfoSources I see
private void SetupKeyMembers(TypeInfo typeInfo) {
IKey key = EntityTypes[typeInfo.FullName].FindPrimaryKey();
typeInfo.KeyMember = null;
foreach(IProperty property in key.Properties) {
typeInfo.AddKeyMember(typeInfo.FindMember(property.Name));
}
}
I want to use the non table based DbSet as explained here.
I posted the code to GitHub
I think the problem is that not all entities have a key so FindPrimaryKey would return null causing SetupKeyMembers to fail.
I have model:
public class Department
{
public int DepartmentID { get; set; }
[Required]
[UniqueDepartmentName]
public string Name { get; set; }
public List<Person> Persons { get; set; }
}
And DBcontext:
public class InstituteContext : DbContext
{
public InstituteContext (DbContextOptions<InstituteContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().HasIndex(p => p.Name).IsUnique();
}
public DbSet<Institute.Models.Department> Department { get; set; }
}
As you see property "NAME" i make unique.
For validation i create My validation Attribute:
public class UniqueDepartmentName : ValidationAttribute
{
public override bool IsValid(object value)
{
var db = new InstituteContext();
}
}
But i can not create instance of InstituteContext, because constructor need parameters.
How i can create instance of InstituteContext? Or what should i pass to constructor in parameters?
Try this:
public class UniqueDepartmentName : ValidationAttribute
{
public override bool IsValid(object value)
{
var connectionString = "Your ConnectionString"
var options = new DbContextOptionsBuilder<InstituteContext>()
.UseSqlServer(new SqlConnection(connectionString)).Options;
using (var dbContext = new BloggingContext(options))
{
// Do necessary staffs here with dbContext
}
}
}
Your DbContextOptions method is in the wrong place, your constructor can be empty, and you need to add the method OnConfiguring, which receives the DbContextOptions.
Something like:
public DbSet<Department> Department { get; private set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) {
// In my case I'm passing the connection string in this method below
options.UseSqlServer("Data Source=DATABASEIP;Initial Catalog=DATABASETABLE;" +
"User ID=USER;Password=PASSWORD");
}
I work with Asp.Net Core WebApi project.
Can I add my tables to IdentityDbContext, like this:
public class ApplicationDbContext : IdentityDbContext<User>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
public DbSet<ProgrammerRole> ProgrammerRoles { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<SubProject> SubProjects { get; set; }
public DbSet<Report> Reports { get; set; }
}
Or do I need to create a second DbContext. And if i create a second DbContext
how can I communicate wiht User in IdentityDbContext.
And my second question:
If i add data in IdentityDbContext, like above, How do I get the data from my tables in ApplicationDbContext?
Because i need to pass DbContextOptions object every time I create a new instance оf ApplicationDbContext. I do this in Startup.cs:
// ===== Add DbContext ========
var connectionString = Configuration.GetConnectionString("DbConnection");
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
I saw in older version of Asp.Net Core, that i can pass Connection String in IdentityDbContext constructor, but now only DbContextOptions.
And i can't do, for example this:
[AllowAnonymous]
[HttpGet]
public IEnumerable<Project> GetRoles()
{
using (var db = new ApplicationDbContext())
{
return db.Projects;
}
}
Can I add my tables to IdentityDbContext, like this:
Yes, it is how you create custom tables. You do not need to create another DbContext. E.g.
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Project> Projects { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Project>(entity =>
{
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
});
base.OnModelCreating(builder);
}
}
Note: you might need to run dotnet ef migrations add Initial and dotnet ef database update for database migration.
using (var db = new ApplicationDbContext()) {...}
You should not create or manage ApplicationDbContext inside controller. If you do so, they become tightly coupled, and you cannot implement unit tests.
Instead, you let dependency inversion (DI) container manage it for you. E.g.
public class UserController : Controller
{
private readonly ApplicationDbContext _context;
public UserController(ApplicationDbContext context)
{
_context = context;
}
[AllowAnonymous]
[HttpGet]
public IEnumerable<Project> GetRoles()
{
return _context.Projects;
}
}
I solve my problem, i just replaced code in my ApplicationDbContext, and get connection string from method:
public class ApplicationDbContext : IdentityDbContext<User>
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(GetConnectionString());
}
private static string GetConnectionString()
{
const string databaseName = "EmployeeReportsDb";
const string databasePass = "SuperPuper_Random_DB-key!";
return $"Server=localhost;" +
$"database={databaseName};" +
$"Trusted_Connection = True;" +
$"MultipleActiveResultSets = True;" +
$"pwd={databasePass};" +
$"pooling=true;";
}
public DbSet<ProgrammerRole> ProgrammerRoles { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<SubProject> SubProjects { get; set; }
public DbSet<Report> Reports { get; set; }
}
here is the resource: https://medium.com/#ozgurgul/asp-net-core-2-0-webapi-jwt-authentication-with-identity-mysql-3698eeba6ff8
As I'm developping an asp net core + ef core 2.0 with localized objects in my model, I adapted the solution provided in the following link to localize my objects link.
I'm now trying to find a clean way to update my collection of translation when updated object are received in the controller.
For the moment I have a step model class defined this way :
public class Step
{
//Native properties
public Guid ID { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Nullable<Guid> ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//MUI Properties
public TranslationCollection<StepTranslation> Translations { get; set; }
public string Description { get; set; }
//{
// get { return Translations[CultureInfo.CurrentCulture].Description; }
// set { Translations[CultureInfo.CurrentCulture].Description = value; }
//}
public Step()
{
//ID = Guid.NewGuid();
Translations = new TranslationCollection<StepTranslation>();
}
}
public class StepTranslation : Translation<StepTranslation>
{
public Guid StepTranslationId { get; set; }
public string Description { get; set; }
public StepTranslation()
{
StepTranslationId = Guid.NewGuid();
}
}
Translation and translationCollection are the same as in the link
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
// indexer
}
public T this[string culture]
{
//indexer
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
public bool HasProperty(string name)
{
return this.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.Name == name);
}
}
My issue in this sample is how to deal correctly with the PUT method and the Description property of my step controller. When it receive a Step object to update (which is done through a native c# client) only the string Description property of Step might have been created/updated/unchanged. So I have to update/create/do Nothing on the Description of the translation in the correct culture.
My first guess is to add in the TranslationCollection class a method in which I could pass the culture, the name of the property to update or not (Description in this case) and the value of the Description.
But as the TranslationCollection is a collection of abstract objects I don't even if this is a good idea and if it's possible.
If someone would have any advice on it (hoping I was clear enough) it would be great !
Finally answered my own question, and it was quite simple.
Just had to use the indexer like :
myobject.Translations[userLang].Name = value;
I am using Table Per Hierarchy (TPH) architecture to work with an existing database schema. When I attempt to use a custom Discriminator field name a DbEntityValidationException:
Property: CourseType Error: The CourseType field is required.
Models
public abstract class Course {
public int ID { get; set; }
public string Name { get; set; }
public string CourseType { get; set; }
}
public class OnlineCourse : Course {
public string Url { get; set; }
}
public class OnsiteCourse : Course {
public string Location { get; set; }
}
Entity Type Configurations
public class CourseMap : EntityTypeConfiguration<Course> {
public CourseMap() {
this.HasKey(x => x.ID);
this.Property(x => x.Name).HasMaxLength(100).IsRequired();
this.Property(x => x.CourseType).HasMaxLength(128).IsRequired();
}
}
public class OnlineCourseMap : EntityTypeConfiguration<OnlineCourse> {
public OnlineCourseMap() {
this.Property(x => x.Url).HasMaxLength(500);
}
}
public class OnsiteCourseMap : EntityTypeConfiguration<OnsiteCourse> {
public OnsiteCourseMap() {
this.Property(x => x.Location).HasMaxLength(100);
}
}
Data Context
public class EntityContext : DbContext {
public EntityContext(): base("Name=EntityContext") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new CourseMap());
modelBuilder.Configurations.Add(new OnlineCourseMap());
modelBuilder.Configurations.Add(new OnsiteCourseMap());
modelBuilder.Entity<Course>()
.Map<OnlineCourse>(x => x.Requires("CourseType").HasValue("Online"))
.Map<OnsiteCourse>(x => x.Requires("CourseType").HasValue("Onsite"));
}
public DbSet<Course> Courses { get; set; }
}
Code Executed
using (var ctx = new EntityContext()) {
ctx.Courses.Add(new OnlineCourse() {
Name = "Online 1",
Url = "Http://www.online.com"
});
ctx.SaveChanges();
}
I would have expected an exception like "CourseType cannot be used as discriminator and also be a mapped coulmun" (I evidently don't remember the correct error message). But CourseType can't be part of the model as a mapped column when it is a discriminator. The usual pattern is to see it in the mapping configuration only (as you have). Nowhere else.
So it may help if you remove CourseType from Course. I hope you don't need it for any other logic.