I currently design table for customer and staff for my ecommerce app and I am using asp.net core identity. I want to know if I should use 1 table user (aka aspnetuser) for staff and customer or should I separate them and use user id as foreign key? If separating them 2 new table with foreign key is user id, how can I use user manager for creating account for staff and customer?
Thanks.
You can extend the base IdentityUser class in order to create a table with additional fields, like:
public class MyIdentityUser : IdentityUser<string>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Type { get; set; }
...
where the Type field could be Staff or Customer.
Or you can use one or more additional table and use the Id as defined in IdentityUser class from Microsoft.ASpNetCore.Identity:
public class IdentityUser<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Initializes a new instance of <see cref="IdentityUser{TKey}"/>.
/// </summary>
public IdentityUser() { }
/// <summary>
/// Initializes a new instance of <see cref="IdentityUser{TKey}"/>.
/// </summary>
/// <param name="userName">The user name.</param>
public IdentityUser(string userName) : this()
{
UserName = userName;
}
/// <summary>
/// Gets or sets the primary key for this user.
/// </summary>
[PersonalData]
public virtual TKey Id { get; set; }
...
you can define the type of your Id field, like my previous example, and use the same type on a related field (like IdentityUserId) in your custom table/tables.
If you extend the base IdentityUser class you need to create a derived context with this declaration, like this:
namespace MyProject.Infrastructure.Contexts
{
public class MyContext : IdentityDbContext<MyIdentityUser>
{
...
Related
In EF Core, when defining Relationships, one can either provide the necessary FK properties explicitly or not:
Explicit FK property:
public class Person
{
public Guid Id { get; set; }
public ICollection<ParentIdentity> Identities { get; set; }
...
}
public class PersonIdentity
{
public Guid Id { get; set; }
public PersonFK { get; set; } //Explicit Data storage FK field in System Logic Entity :-(
...
}
The relationship would be defined in Fluent API as follows:
model.HasMany(x => Identities) // Person can have multiple identities
.WithOne() // Identity does not need a Nav property back up to Person
.WithForeignKey(x => x.PersonFK) // Hardcoded the FK.
The upside is its eminently clarity of how it's hooked up.
The downside is the blurring of domains between system logic and storage -- in that the system entity now has Data storage specific attributes (PersonFK) that have nothing to do with system logic that developers should be concentrating on.
Shadow properties
The alternative is to let EF sort it out, using shadow properties, by not define an FK Property on the Entity:
public class Person
{
public Guid Id { get; set; }
public ICollection<ParentIdentity> Identities { get; set; }
...
}
public class PersonIdentity
{
public Guid Id { get; set; }
...
}
And define the relationship as follows:
model.HasMany(x => Identities) // Person can have multiple identities
.WithOne() // Identity does not need a Nav property back up to Person
//.WithForeignKey(x => x.PersonFK) // Don't provide an FK property
;
EF will step up and add a property to the db table named to the following convention:
<principal primary key property name>Id
//ie, will be created as `PersonId`
But let's say I want to change it to:
<principal primary key property name>FK
//ie, will be created as `PersonFK`
Question
How?
Foraging so far
In case it helps, I'm looking in the following direction:
I can see a SqlServerConventionSetBuilder that inherits from RelationalConventionSetBuilder that inherits from ProviderConventionSetBuilder.
ProviderConventionSetBuilder in turn calls
ForeignKeyIndexConvention
ForeignKeyPropertyDiscoveryConvention
ForeignKeyAttributeConvention
found some sparse documentation at https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.conventions.foreignkeyindexconvention?view=efcore-6.0
but not enough there to know where to look really.
Can someone point me in the right direction as to:
what convention to replace
how to replace it easily?
Thank you!
Given the following code example, how do you implement this in entity framework core where you do not want a table created for the base class but you do for the derived class and the primary key is defined in the base class?
public class JobBase
{
public JobBase() { }
public Guid JobId { get; set; } = Guid.NewGuid();
public string Title { get; set; }
}
public class Job : JobBase
{
public Job() { }
public String AdditionalInformation { get; set; }
}
And here is what I have in my DBContext class:
public DbSet<Job> Jobs { get; set; }
var job = mb.Entity<Job>();
job.HasKey(aa => aa.JobId);
job.Property(aa => aa.JobId).HasColumnName("JobId");
I currently get the following error when trying to add-migration:
A key cannot be configured on 'Job' because it is a derived type. The key must be configured on the root type 'JobBase'. If you did not intend for 'JobBase' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.
The only part of the error message I am doing is JobBase is referenced from a navigation on a type that is included in the model but doing that is the whole reason i implemented this relationship in the first place so i can't just remove that navigation.
modelBuilder.Entity<Job>().HasKey(x => x.JobId).ToTable("Job");
EF Inheritance and Primary Keys
I have an inheritance hierarchy setup that I am mapping to a DB via TPT in Code first. For the most part the hierarchy is one level deep, but sometimes it it two. My base class looks like this:
public class AuditEvent
{
public int AuditEventID;
//other stuff
};
Then I have a bunch of other classes that look like this (with different names and properties):
public class PageRequest : AuditEvent
{
/// <summary>
/// Page Request Id (Primary Key)
/// </summary>
public Int64 PageRequestID { get; set; }
/// <summary>
/// Screen (page) being requested
/// </summary>
public string Screen { get; set; }
/// <summary>
/// Http Method
/// </summary>
public string HttpMethod { get; set; }
/// <summary>
/// Confirmation Logs linked to this page request
/// </summary>
public virtual List<ConfirmationLog> ConfirmationLogs { get; set; }
}
This specific class (PageRequest) is a parent to one other class called ConfirmationLog, which looks like this:
/// <summary>
/// Object used to log confirmations to the auditing database
/// </summary>
public class ConfirmationLog : PageRequest
{
/// <summary>
/// Confirmation ID
/// </summary>
public long ConfirmationID { get; set; }
/// <summary>
/// Confirmation number
/// </summary>
public string ConfirmationNum { get; set; }
/// <summary>
/// Web action ID (automated alert or transaciton confirmation number)
/// </summary>
public int WebActionID { get; set; }
}
I'm configuring the mappings using configuration classes and the fluent API, like so:
/// <summary>
/// Configuration class for PageRequest
/// </summary>
public class PageRequestConfiguration : EntityTypeConfiguration<PageRequest>
{
/// <summary>
/// Default constructor
/// </summary>
public PageRequestConfiguration()
{
//Table
ToTable("PageRequests");
//primary key
HasKey(a => a.PageRequestID);
//Properties
Property(a => a.PageRequestID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(a => a.Screen).IsRequired().HasMaxLength(100);
Property(a => a.HttpMethod).IsRequired().HasMaxLength(10);
}
}
/// <summary>
/// Confirmation Log configuration class. Configures the confirmation log class for the db model
/// </summary>
public class ConfirmationLogConfiguration : EntityTypeConfiguration<ConfirmationLog>
{
/// <summary>
/// Default constructor
/// </summary>
public ConfirmationLogConfiguration()
{
//Map to Table
ToTable("ConfirmationLogs");
//Primary Key
HasKey(a => a.ConfirmationID);
//required fields
Property(a => a.ConfirmationID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(a => a.PageRequestID).IsRequired();
Property(a => a.ConfirmationNum).IsRequired().HasMaxLength(12);
Property(a => a.WebActionID).IsRequired();
}
}
I then create a rather large LINQ query based on this hierarchy. I'll spare that query because it's composed in about 10 steps, and I don't think that's the source of my problem. The problem is, when I run the query, the SQL generated for some reason thinks that the column AuditEventID (the primary key for the base class), exists on the ConfirmationLogs table (the grandchild table). The ConfirmationLogs table has a foreign key to it's parent table (PageRequests), which then has the foreign key to it's parent table (AuditEvents).
My question is, did I set this hierarchy up wrong? Does the "grandchild" table need the foreign key to both it's parent and grandparent for this to function? (if it does I find that unfortunate).
I'm positive that the inheritance relationship is throwing things off because if I don't make ConfirmationLogs a child of PageRequests and configure the relationship to PageRequests with HasRequired()/WithMany(), things work fine.
Any help would be appreciated.
Update
So, after further investigation I think there is a general problem with the way I'm trying to use inheritance. I should note that I'm trying to map code first to an existing database. In the database, I have my AuditEvent table, and a bunch of "child" tables like PageRequest. Page request has it's own primary key called PageRequestID, as well as a foreign key called AuditEventID. The other child tables are setup the same way. In my Configuration class for PageRequest (listed above), I'm trying to map this by using the HasKey function to say that the PageRequestID is the primary key, and assuming that EF will know about the foreign key AuditEventID by convention and inheritance. I should also note that I can write to the DB using the model just fine. If I want to write a PageRequest, I create PageRequest object, populate all the required fields as defined by both the PageRequest and AuditEvent base class, and save through the context. EF creates the AuditEvent record, and the pageRequest record with the FK back to AuditEvent.
What makes me think I'm not using inheritance right is that I allowed EF to create my database for me, using the model and mapping I've created. For the PageRequest table (and all other child tables), EF actually created a primary key called AuditEventID (even though my configuration is telling it to do otherwise). This key is also labeled as a foreign key, and the column that I want to create as a primary key (PageRequestID in this example) is just configured as being required (non-nullable). So it appears that EF taking the primary key from my BASE class and using that as a primary key AND foreign key in my child classes, almost like the concept of the AuditEventID is spread between the parent and child tables. Is there a way to change this behavior?
You are saying this didn't work, and it still expected an AuditRequestID in the table that had the ConfirmationLog object? I'm looking at the reference: Specifying Not to Map a CLR Property to a Column in the Database in http://msdn.microsoft.com/en-us/data/jj591617#1.6
public ConfirmationLogConfiguration()
{
//Map to Table
ToTable("ConfirmationLogs");
//Primary Key
HasKey(a => a.ConfirmationID);
//required fields
Property(a => a.ConfirmationID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(a => a.PageRequestID).IsRequired();
Property(a => a.ConfirmationNum).IsRequired().HasMaxLength(12);
Property(a => a.WebActionID).IsRequired();
Ignore(a => a.AuditEventID);
}
Good luck.
I have the following context:
public class DataContext : DbContext
{
/// <summary>
/// Gets or sets Addresses.
/// </summary>
public DbSet<Address> Addresses { get; set; }
/// <summary>
/// Gets or sets Users.
/// </summary>
public DbSet<Users> Users { get; set; }
}
I my application user may change data in say user data and then he may want to cancel changes. The best way to do this, is to refresh the DataContext from the database. But DbContext has no Refresh method. How can I refresh my DataContext?
You can reload the entity from the database as follows.
context.Entry(user).Reload();
Or you can try out the methods described in this question.
I've the following table definition in MSSQL:
CREATE TABLE [User] (
[Id] bigint identity(1,1) NOT NULL,
[Email] nvarchar(256),
[PasswordHash] nvarchar(128) NOT NULL,
[PasswordFormat] int DEFAULT ((0)) NOT NULL,
[PasswordSalt] nvarchar(10) NOT NULL,
[Timestamp] timestamp
)
;
The EDMX property for Timestamp looks like this: (Note only the red property has been manually changed by me)
I used the t4 template to automatically generate POCO entities.
The User entity looks like this:
public partial class User : IEntity
{
public virtual long Id
{
get;
set;
}
...
[TimestampAttribute]
[ConcurrencyCheck]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Autogenerated by T4.")]
public virtual byte[] Timestamp
{
get;
set;
}
...
}
When doing a 'SaveChanges' operation on the ObjectContext, I get a validation error for the User entity which is called : The Timestamp field is required
Solution:
I've changed the T4 generated User class to: (removed the 'ConcurrencyCheck' attribute)
public partial class User : IEntity
{
public virtual long Id
{
get;
set;
}
...
[TimestampAttribute]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Autogenerated by T4.")]
public virtual byte[] Timestamp
{
get;
set;
}
...
}
And I've added a generic metadata class which is used by all Entities which excludes the Timestamp property :
/// <summary>
/// A MetaData which defines some default metadata for an Entity
/// </summary>
public class EntityMetaData
{
/// <summary>
/// Initializes a new instance of the <see cref="EntityMetaData"/> class.
/// </summary>
protected EntityMetaData()
{
}
/// <summary>
/// Gets or sets the timestamp.
/// Note : this field is excluded on the client.
/// </summary>
/// <value>The timestamp.</value>
[Exclude]
public byte[] Timestamp { get; set; }
}
This solves the issue.
One option is to set Nullable attribute to true in EDMX model but keep NOT NULL constraint in the database.
As generated type for Timestamp (RowVersion) is reference type (byte[]) and thus can accept null value, it should not break any existing code.