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.
Related
I'm running with the code below and I can see that one key is created in the table [DataProtectionKeys]
services.AddDataProtection()
.SetApplicationName(dataProtectionSettings.ApplicationName)
.ProtectKeysWithCertificate(serviceCertificate)
.UnprotectKeysWithAnyCertificate(serviceCertificate)
.PersistKeysToDbContext<DataProtectionContext>();
I'm using this database context (am I missing something?):
class DataProtectionContext : DbContext, IDataProtectionKeyContext
{
/// <summary>
/// A recommended constructor overload when using EF Core with dependency injection.
/// </summary>
/// <param name="options"></param>
public DataProtectionContext(DbContextOptions<DataProtectionContext> options)
: base(options) { }
/// <summary>
/// This maps to the table that stores keys.
/// </summary>
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
But, if I change to ApplicationName string value to something else, I don't see that a new key is created
Any idea why or how to fix it so this database table can support multiple application
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>
{
...
Say I have a "Relationship" entity:
public class Relationship
{
[Key]
[Required]
public int RelationshipId { get; set; }
[Required]
public int FriendOneId { get; set; }
public virtual User FriendOne{ get; set; }
[Required]
public int FriendTwoId { get; set; }
public virtual User FriendTwo { get; set; }
}
If I want to map these relationships with ModelBuilder, what is the difference between this:
modelBuilder.Entity<Relationship>()
.HasRequired(c => c.FriendOne)
.WithMany()
.HasForeignKey(u => u.FriendOneId);
And this:
modelBuilder.Entity<Relationship>()
.HasRequired(c => c.FriendOne)
.WithMany()
.HasForeignKey(u => u.RelationshipId);
I get confused with this every time I'm setting up a new DB. The documentation I've found and answers on SO seem to conflict one another on this... any help in understanding how to use HasForeignKey would be much appreciated.
modelBuilder.Entity<ThisT>() //configure model for entity type <T>
.HasRequired(c => c.FriendOne) // if a field, ef will create on DB as Not Null, and check in context
// if it is a navigation entity, then an underlying FK field will be marked as Not null .
// A new field will be introduce to manage this if not declared
.WithMany() // the target of foreign key can many Entity<t> pointing at it.
// The Many target could also have ICOllection<ThisT>.
// ie .withMany(MainT=>MainT.BucketOfThem)
// leave it empty if the other side doesnt track related
.HasForeignKey(u => u.RelationshipId); // dont create a field, I have one already..
// the Key to Table behind FriendOne is called RelationshipId
standard EF docu on withMany knows that call is chained.
ie first you a HasRequired, then a WithMany.
so you are in 1:M config mode.
/// <summary>
/// Configures the relationship to be required:many with a navigation property on the other side of the relationship.
///
/// </summary>
/// <param name="navigationPropertyExpression">An lambda expression representing the navigation property on the other end of the relationship. C#: t => t.MyProperty VB.Net: Function(t) t.MyProperty </param>
/// <returns>
/// A configuration object that can be used to further configure the relationship.
/// </returns>
To try to isolate the source of a bug I have built the following:
SQL Server table:
CREATE TABLE [dbo].[Names]
([Key] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Names] PRIMARY KEY CLUSTERED ([Key] ASC)
)
GO
ALTER TABLE [dbo].[Names] ADD CONSTRAINT [DF_Names_Key] DEFAULT (newid()) FOR [Key]
When I add rows to this table via the SQL Server Management Studio the guid gets a value as expected.
I then create an EF model with this table, this generated the following code:
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmEntityTypeAttribute(NamespaceName="Model1", Name="Names")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class Names : EntityObject
{
#region Factory Method
/// <summary>
/// Create a new Names object.
/// </summary>
/// <param name="key">Initial value of the Key property.</param>
public static Names CreateNames(global::System.Guid key)
{
Names names = new Names();
names.Key = key;
return names;
}
#endregion
#region Primitive Properties
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Guid Key
{
get
{
return _Key;
}
set
{
if (_Key != value)
{
OnKeyChanging(value);
ReportPropertyChanging("Key");
_Key = StructuralObject.SetValidValue(value);
ReportPropertyChanged("Key");
OnKeyChanged();
}
}
}
private global::System.Guid _Key;
partial void OnKeyChanging(global::System.Guid value);
partial void OnKeyChanged();
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
[DataMemberAttribute()]
public global::System.String Name
{
get
{
return _Name;
}
set
{
OnNameChanging(value);
ReportPropertyChanging("Name");
_Name = StructuralObject.SetValidValue(value, true);
ReportPropertyChanged("Name");
OnNameChanged();
}
}
private global::System.String _Name;
partial void OnNameChanging(global::System.String value);
partial void OnNameChanged();
I then created a test to add data to the table:
[TestMethod]
public void WriteTestMethod1()
{
using (Model1Container context = new Model1Container())
{
Names n = new Names();
n.Name = "T2";
context.Names.AddObject(n);
context.SaveChanges();
}
}
The result of this is that the row is inserted but the Guid is Empty (all zeros)
Why is this happening? Can EF work with database generated Guids?
This is old, but here is an answer anyway, since it's still relevant.
Your test code creates an instance of class Names and assigns a value to the Name property.
You don't explicitly assign anything to the Key property. The Key property is of type Guid, which is a struct (value type), not a class (reference type), so it can never be null and will always have a value, if just the default. The uninitialized default value of a Guid is all zero fields.
When you add/persist this entity instance, EF generates an INSERT to both fields, since values exist for both.
The default value expression defined in your schema is not used because a value was provided in the INSERT statement from the client side.
You can get a server generated uid value by defining the Key field as nullable, which will correspond with field type in C# of Guid? (rather than Guid). Then you can allow the Key value to remain null in your code and the server defined default expression will be used.
However, if this field is your primary key, a nullable field may not be what you need. When I'm in this situation I use a constructor that sets a Guid value (yes, on the client side) rather than allowing it to take the default value.
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.