How to seed an enum in Entity Framework Core 2.1 - asp.net

I am new to EF Core, and am trying to seed an enum.
According to Data Seeding, this feature is new to EF Core 2.1.
I reviewed several solutions including this SO solution by Blake Mumford, but this doesn't work for me since an enum is not a reference type.
My goal (with the help from migrations) is to have the Category enum to be populated in a new SQL table called Category, and have my Payment table contain a column that references the Category table as a foreign key.
Any help would be greatly appreciated. Thanks.
public partial class Payment
{
public int PaymentId { get; set; }
public DateTime PostedDate { get; set; }
public string Vendor { get; set; }
public decimal Amount { get; set; }
public virtual Category Category { get; set; }
}
public enum Category
{
Restaurants,
Groceries,
[Display(Name = "Home Goods")]
HomeGoods,
Entertainment
}
public partial class MyDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Error: The type Category must be a reference type in order to use it as parameter TEntity in the
// genric type or method ModelBuilder.Entity<TEntity>()
modelBuilder.Entity<Category>().HasData(Category.Restaurants,
Category.Groceries,
Category.HomeGoods,
Category.Entertainment);
}
}

This is what I did hopefully it will work with your environment.
Environment
Project Framework
.NetCore 3.0
Nugets:
EFCore 3.1.2
EFCore.Design 3.1.2
EFCore.Tools 3.1.2
EFCore.Relational 3.1.2
EFCore.SqlServer 3.1.2
Implementation
Add OnModelCreating
public partial class MyDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Error: The type Category must be a reference type in order to use it as parameter TEntity in the
// genric type or method ModelBuilder.Entity<TEntity>()
modelBuilder.Entity<UserRole>().HasData(EnumFunctions.GetModelsFromEnum<UserRole, UserRoleEnum>());
}
}
Create a Generic Enum Function
public static class EnumFunctions
{
public static IEnumerable<TModel> GetModelsFromEnum<TModel, TEnum>() where TModel : IEnumModel<TModel, TEnum>, new()
{
var enums = new List<TModel>();
foreach (var enumVar in (TEnum[])Enum.GetValues(typeof(TEnum)))
{
enums.Add(new TModel
{
Id = enumVar,
Name = enumVar.ToString()
});
}
return enums;
}
}
Create an Interface
public interface IEnumModel<TModel, TModelIdType>
{
TModelIdType Id { get; set; }
string Name { get; set; }
}
Apply the interface to the model
[Table("UserRoles")]
public class UserRole : IEnumModel<UserRole, UserRoleEnum>
{
[Key]
public UserRoleEnum Id { get; set; }
public string Name { get; set; }
}
Add your migration
PM> add-migration SeedUserRoleTable
You should see the migration added
Update the database with the seeded info
PM> update-database

Under my Entities folder I have added Enumerations folder, I have this:
public class UserStatus : Enumeration
{
public static readonly UserStatus New = new UserStatus(1, "New");
public static readonly UserStatus UnderReview = new UserStatus(2, "UnderReview");
public static readonly UserStatus Customer = new UserStatus(3, "Customer");
public static readonly UserStatus Approved = new UserStatus(4, "Approved");
public static readonly UserStatus Declined = new UserStatus(5, "Declined");
public UserStatus(int id, string name)
: base(id, name)
{
}
}
In my DataContext.cs I just use this to seed data:
modelBuilder.Entity<UserStatus>().HasData(Enumeration.GetAll<UserStatus>());
UserStatus is not declared as DbSet in DataContext.cs
.NET Core 3.1

You cannot seed values for an enum type to the database because enums are not entity types and thus do not go into the database in the first place. In other words, there will be no Categories table, so it makes no sense to seed values into that non-existent table.
If you want to actually have your categories be persisted in your database, then you need to create a class like:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}

Related

is there any way to pre-define a query in a model that runs whenever that model is called from context

i have model in my mvc 5 project named operations.
public class Operation
{
public int OperationID { get; set; }
public string Name { get; set; }
public decimal Commission { get; set; }
public int DepartmentID { get; set; }
public bool CommissionValidity { get; set; }
public bool IsHidden { get; set; }
public decimal Capacity { get; set; }
public virtual Department Department { get; set; }
}
requirement is whenever this model is called from context
like below
Var db = new ApplicationDbContext();
var Operations = db.Operations.tolist();
i want only the records to be loaded where IsHidden field is False without using where clause in query
is there any way like i make constructor or define getter Setter functions to acquire the goal.
If you're using Entity Framework Core, you can use a global query filter to achieve this.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Operation>().HasFilter(o => !o.IsHidden);
}
Entity Framework 6 doesn't have query filters. For that, you'd need to use a different property on your DbContext to return your visible entities:
public IQueryable<Operation> VisibleOperations
=> Set<Operation>().Where(o => !o.IsHidden);
However, you won't be able to use this filtered query to add or remove entities from the database, so you'll probably still need to expose the full IDbSet<Operation>, which won't have the filter applied.

AspNetRoles table not showing custom fields

I have recently extended my AspNetRoles table like this:
public class AspNetRoles:IdentityRole
{
public AspNetRoles() : base() { }
public String Label { get; set; }
public String ApplicationId { get; set; }
public AspNetApplications Application { get; set; }
public static readonly String SystemAdministrator = "SystemAdministrator";
}
It works fine when I create a new role. However, when I try to extract it to a list like this:
var data = dbContext.Roles.ToList();
And try to do a search like this:
data = data.Where(u => u.Id.ToString().ToLower().Contains(Input.Search.ToLower())).ToList();
I can't access the ApplicationId column. Am I missing something?
EDIT:
My dbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, AspNetRoles<string>, string>
{
public virtual DbSet<AspNetUsersExtendedDetails> AspNetUsersExtendedDetails { get; set; }
public virtual DbSet<AspNetApplications> AspNetApplications { get; set; }
public virtual DbSet<AspNetEventLogs> AspNetEventLogs { get; set; }
public virtual DbSet<AspNetRoles> AspNetRoles { get; set; }
public ApplicationDbContext() : base("AppStudio")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I have updated my dbContext but now it shows this error: 'IdentityDbContext<ApplicationUser, AspNetRoles<string>, string>' does not contain a constructor that takes 2 arguments
You need to tell ASP.Net Identity about the custom role table that you want to use.
Edit: since the default IdentityRole implementation uses a string as the PK, the type can be omitted. Just checking futher on ASP.Net Identity version 2, as soon as you specify a custom IdentityRole class, the class declaration needs to include all types.
That means you need to declare your ApplictionDbContext like this:
public class ApplicationDbContext: IdentityDbContext<ApplicationUser, AspNetRoles, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public ApplicationDbContext() : base("AppStudio")
{
//note: before this change, if you included the
//throwIfV1Schema parameter in the constructor,
//it needs to be removed.
}
//implementation
}
Note that this assumes that the Primary Key of the users table is a string. If this is not the case, substitute with the applicable type (e.g. a Guid).
You have to add AspNetRoles to your IdentityDbContext.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, AspNetRoles, string>
{
public virtual DbSet<AspNetUsersExtendedDetails> AspNetUsersExtendedDetails { get; set; }
public virtual DbSet<AspNetApplications> AspNetApplications { get; set; }
public virtual DbSet<AspNetEventLogs> AspNetEventLogs { get; set; }
public ApplicationDbContext() : base("AppStudio", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}

XMLserializer, Entity Framework : Cannot serialize member of type ICollection see inner exception for more details

I want to map XML elements into my database table (using Entity Framework):
var xmlSerializer = new XmlSerializer(typeof(Participant), new XmlRootAttribute("participant"));
var participant = (Participant)xmlSerializer.Deserialize(new StringReader(content));
I have Participant table which I can access by
[XmlRoot("participant", Namespace = "")]
public partial class Participant
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Participant()
{
this.GroupParticipant = new HashSet<GroupParticipant>();
this.ParticipantAddress = new HashSet<ParticipantAddress>();
this.ParticipantPublisher = new HashSet<ParticipantPublisher>();
this.ParticipantJob = new HashSet<ParticipantJob>();
this.ParticipantProvider = new HashSet<ParticipantProvider>();
}
[XmlElement("firstName")]
public string FirstName { get; set; }
[XmlElement("lastName")]
public string LastName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
//[XmlElement("address")]
//[XmlElement("address")]
//[XmlArray("HashSet<ParticipantAddress>"), XmlElement("address")]
//[XmlArrayItem("ICollection<ParticipantAddress>")]
//[XmlAttribute(DataType = "ICollection<ParticipantAddress>", AttributeName = "address")]
[XmlElement("address", typeof(List<ParticipantAddress>))]
public virtual ICollection<ParticipantAddress> ParticipantAddress { get; set; }
}
ParticipantAddress is ICollection:
[Serializable]
[XmlInclude(typeof(HashSet<ParticipantAddress>))]
public partial class ParticipantAddress
{
public int ParticipantAddressId { get; set; }
public int ParticipantId { get; set; }
[XmlElement("city")]
public string City { get; set; }
[XmlElement("state")]
public string State { get; set; }
[XmlElement("zipCode")]
public string ZipCode { get; set; }
public virtual Participant Participant { get; set; }
}
Exception says:
{"There was an error reflecting type 'x.Participant'."}
My inner Exception says:
{"Cannot serialize member 'xParticipant.ParticipantAddress' of type 'System.Collections.Generic.ICollection`1[[x.ParticipantAddress, APS.Data.BatchInterface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]', see inner exception for more details."}
I am reading XML by streamReader.
I have tried
[XMLArray]
Changing ICollection to List
make class serializable
Is there any other way to overcome this problem or any examples related to my question or any changes I need to implement in my code?
ICollection is not serializable.
- You can use DTO.
- You can change the collection type (i.e. with List<>) and with XML serialization attributes avoid circular references and/or disable lazy load (i.e. use eagerly load using Include method) or the risk is that you serialize the whole database.
You have this issue because of the virtual properties. You try to serialize a class which has a reference to another class, which has a reference to the first, class, which... endless loop.
If you want to serialize an entity, the best thing you can do is use a DTO class, which is a class used only to export your data. In these classes you can't have virtual properties, but what you can do is include the DTO objects of your ParticipantAddress.
The other thing you can try, if it isn't a necessity to serialize to XML, is use the Newtonsoft.Json package to serialize the entities. The package has some options to deal with navigational properties.
I have created a region and change ICollection<> to List<> because
ICollection is an interface and interfaces are not serializable.
But List<> is a class and this class implements all the below interfaces:
IList, ICollection, IList, ICollection, IReadOnlyList, IReadOnlyCollection, IEnumerable, IEnumerable.
I kept both Icollection as well as List and put [XmlIgnore] on ICollection.
[XmlRoot("participant", Namespace = "")]
public partial class Participant
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Participant()
{
this.GroupParticipantList = new List<GroupParticipant>();
this.ParticipantAddressList = new List<ParticipantAddress>();
this.ParticipantPublisherList = new List<ParticipantPublisher>();
this.ParticipantJobList = new List<ParticipantJob>();
this.ParticipantProviderList = new List<ParticipantProvider>();
}
[XmlElement("firstName")]
public string FirstName { get; set; }
[XmlElement("lastName")]
public string LastName { get; set; }
[XmlIgnore]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ParticipantAddress> ParticipantAddress { get; set; }
#region Custom properties
[XmlElement("address")]
public virtual List<ParticipantAddress> ParticipantAddressList { get; set; }
#endregion
}
But with this option I am having another Problem: If I do any single change in my SQL database and if I do update model from database, then I lose manually implemented code like all the XML sentences in this code.
I answered this in the below article to add [System.Xml.Serialization.XmlIgnore] to entity.tt template
Prevent Property from being serialized
I had a similar issue using EF, to implement a Web service and couldn't serialize the ICollection<> object.
I hopes this helps you out.
public class User
{
public User()
{
sessions = new HashSet<Session>();
}
public int Id { get; set; }
public string Name { get; set; }
[XmlIgnore]
[IgnoreDataMember]
public virtual ICollection<Session> sessions { get; set; }
}
public class Session
{
public int Id { get; set; }
public Datetime start_dtime{ get; set; }
public Datetime end_dtime{ get; set; }
public virtual User user{ get; set; }
}

Why do the ASP.NET Identity interfaces use strings for primary and foreign keys?

I'm looking at the interfaces on the new ASP.NET Identity classes and the database it creates using Entity Framework Code First. I'm using the Visual Studio 2013 RC.
At first glance the database schema looks reasonably normal:
But all the keys are NVARCHAR(128)
And for some crazy reason AspNetUserSecrets.Id is a PK that looks like it could point to more than one record in the AspNetUsers table. Does this mean multiple AspNetUsers will have to share the same password?
When I look at the Looking at the interfaces you're forced to implement, these are all strings...
public class User : IUser
{
public string Id { get; set; }
public string UserName { get; set; }
}
public class UserSecret : IUserSecret
{
public string UserName { get; set; }
public string Secret { get; set; }
}
public class UserRole : IUserRole
{
public string UserId { get; set; }
public string RoleId { get; set; }
}
public class UserClaim : IUserClaim
{
public string UserId { get; set; }
public string ClaimType { get; set; }
public string ClaimValue { get; set; }
}
public class UserManagement : IUserManagement
{
public string UserId { get; set; }
public bool DisableSignIn { get; set; }
public DateTime LastSignInTimeUtc { get; set; }
}
public class Tokens : IToken
{
public string Id { get; set; }
public string Value { get; set; }
public DateTime ValidUntilUtc { get; set; }
}
public class UserLogin : IUserLogin
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
}
public class Role : IRole
{
public string Id { get; set; }
public string Name { get; set; }
}
So I'm coming to terms with the fact that I may have to implement this using strings for PK and FK relationships.
But I'd really love to know WHY it's built like this...?
EDIT: Time has passed and there are now articles on how to extend the asp.net identity to use int (or guid) fields:
http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity
The intent was to allow both arbitrary id types (i.e. int, guid, string), but also avoid having serialization/casting issues for the id property.
So you can define your keys however you like and just implement the interface method
public class MyUser : IUser {
public int Id { get; set; }
string IUser.Id { get { return Id.ToString(); } }
}
Adding to what Hao said:
The Identity runtime prefers strings for the user ID because we don’t want to be in the business of figuring out proper serialization of the user IDs (we use strings for claims as well for the same reason), e.g. all (or most) of the Identity interfaces refer to user ID as a string.
People that customize the persistence layer, e.g. the entity types, can choose whatever type they want for keys, but then they own providing us with a string representation of the keys.
By default we use the string representation of GUIDs for each new user, but that is just because it provides a very easy way for us to automatically generate unique IDs.
With ASP.NET Core, you have a very simple way to specify the data type you want for Identity's models.
First step, override identity classes from < string> to < data type you want> :
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
Declare your database context, using your classes and the data type you want :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
And in your startup class, declare the identity service using your models and declare the data type you want for the primary keys :
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
In ASP.NET identity tables, primary keys will still be in NVARCHAR but in your application it's will be the data type you want.
You can check this in a controller :
[HttpGet]
public async Task<IActionResult> Test()
{
ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
Guid userId = user.Id; // No cast from string, it's a Guid data type
throw new NotImplementedException();
}

Business Logic Architecture with Entity Framework

I`m using Entity Framework and I have entities like this:
public class User : IEntity
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Required]
public String Email { get; set; }
public virtual ICollection<Project> UserProjects { get; set; }
}
public class Project : IEntity
{
[Key]
public int ProjectId { get; set; }
public String Title { get; set; }
public String Description { get; set; }
[ForeignKey("UserOwner")]
public int UserOwnerId { get; set; }
public virtual User UserOwner { get; set; }
}
Also I use Repository pattern and Unit Of Work pattern.
For example I have method
CreateProject(String title, String description, String userOwnerEmail)
in Projects logic class which contains only Project Repository.
Also i have UserLogic class that allows me get user by his email.
How i can get user by his email in CreateProject method to designate him like a project owner.
The main aim is to create loose coupling method.
I think that this example is bad:
public void CreateNewProject(String projectName, String description,String usersEmail)
{
var usersLogic = kernel.Get<IUsersServices>();
User owner = usersLogic.GetUserByEmail(usersEmail);
unit.Repository<Project>()
.Insert(new Project
{
Title = projectName,
Description = description,
CreationDate = DateTime.Now,
UserOwner = owner,
UsersIncludeedInProject = new List<User>()
});
unit.Save();
}
Business Logic and Entity Framework (or any other ORM) don't belong in the same phrase. Separation of Concerns is the principle.
class Project
{
public Project(IProjectRepository repo, IUsersServices userServ){}
public void CreateNewProject(String projectName, String description,String usersEmail)
{
var owner=_users.GetByEmail(usersEmail);
//create project\\
_repository.Save(project);
}
One other approach is to pass the User object as an argument, object you'll get by asking a UserService or even a UserRepository to GetUserByEmail(). But it will be outside the CreateNewProject method
var user= _usersService.GetByEmail();
var project=project.CreateNewProject(projectName,projectDescription,user);
_projectRepository.Save(project);
In this case CreateNewProject does exactly that, because it's probably not its concern to save the project. I recommend this second approach.

Resources