Multiple Common Fields CreatedOn and CreatedBy in every table of a database. How it can be without repeating for every table - entity-framework

Scenerio:
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public string Description { get; set; }
public DateTime CreatedOn {get; set; }
public string CreatedBy {get; set; }
}
public class TestItem
{
public int TestItemId { get; set; }
public string TestItemName { get; set; }
public Department Department { get; set; }
public int DepartmentId { get; set; }
public DateTime CreatedOn {get; set; }
public string CreatedBy {get; set; }
}
public class Patient
{
public int PatientId { get; set; }
public string PatientName { get; set; }
public DateTime CreatedOn {get; set; }
public string CreatedBy {get; set; }
}
the problem is that, every time I create a table I have to add those two columns repeatedly.
But I want like this-
public class EntryLog
{
public int EntryLogId { get; set; }
public DateTime CreatedOn {get; set; }
public string CreatedBy {get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public string Description { get; set; }
public EntryLog EntryLog { get; set; }
public int EntryLogId { get; set; }
}
and so on...
class A { .. }
class B { .. }
But its creating problem [showing conflicts error with other table's foreign key] while creating a row for a Department or a Patient.
In EF core, there is Table Per Hierarchy (TPH) but in that case every table will be merged into a single table. But that doesn't give me any solution.
looking forward to expert's suggestion...

The bottom line is: use EntryLog as a base type and don't tell EF about it. It's easy enough to keep EF-core oblivious of the base type: only register the derived types. Doing so, EF-core will map your subtypes to their own tables, just as if they didn't have a common type.
Now EntryLog will no longer need an Id, and it should be abstract:
public abstract class EntryLog
{
public DateTime CreatedOnUtc { get; set; }
public string CreatedBy { get; set; }
}
Whether this is enough depends on your specific requirements. There are several possibilities.
1. No additional configuration
If you're happy with the default conventions EF will apply to the common properties, your done. CreatedOnUtc will be mapped to a DateTime2 column (in Sql Server) and CreatedBy to an nvarchar(max) column in each table for an EntryLog entity.
However, if you do need custom configurations --for example if you want to map CreatedBy to an nvarchar(50) column-- additional mapping instructions should be applied. And of course you still want to do the mapping of the common properties only once --which would also happen if you did map the base type in a TPH scheme. How to do that?
2. Data annotations in the base type
The easiest option is to add data annotations:
public abstract class EntryLog
{
public DateTime CreatedOnUtc { get; set; }
[MaxLength(50)]
public string CreatedBy { get; set; }
}
And that's all.
But there are dev teams that don't want to use data annotations for mapping instructions. Also, EF's fluent mappings offer more options than data annotations do. If data annotations don't fit the bill for whatever reason, fluent configurations must be applied. But still, you only want to configure the common properties only once. A viable way to achieve that is to use IEntityTypeConfigurations for each EntryLog and let each concrete configuration derive from a base class. This offers two more options.
3. The base class contains regular properties
Option 4 will make clear why I talk about "regular properties" here. This is what it looks like:
abstract class EntryLogConfiguration
{
public void ConfigureBase<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : EntryLog
{
// Just an example of how to configure a base property.
builder.Property(e => e.CreatedBy).HasMaxLength(50);
}
}
class DepartmentConfiguration : EntryLogConfiguration,
IEntityTypeConfiguration<Department>
{
public void Configure(EntityTypeBuilder<Department> builder)
{
builder.Property(p => p.DepartmentName).HasMaxLength(100);
ConfigureBase(builder);
}
}
And in the context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new DepartmentConfiguration());
}
4. Using shadow properties
Shadow properties is a new feature of EF-core.
Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model. The value and state of these properties is maintained purely in the Change Tracker.
Let's suppose you want to have CreatedBy as a class property (because you want to show it in a UI) but only need CreatedOnUtc as a property that's set in the background and that shouldn't be exposed. Now EntryLog will look like this:
public abstract class EntryLog
{
public string CreatedBy { get; set; }
}
So the property CreatedOnUtc is gone. It has been moved to the base configuration as shadow property:
abstract class EntryLogConfiguration
{
public void ConfigureBase<TEntity>(EntityTypeBuilder<TEntity> builder)
where TEntity : EntryLog
{
builder.Property(e => e.CreatedBy).HasMaxLength(50);
builder.Property<DateTime>("CreatedOnUtc");
}
}
Now you can't set CreatedOnUtc directly, only through EF's change tracker. The best place to do that is in an override of SaveChanges in the context:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<EntryLog>())
{
entry.Property<DateTime>("UpdatedOnUtc").CurrentValue = DateTime.UtcNow;
}
return base.SaveChanges();
}
Of course, if UpdatedOnUtc was a regular property, this override would also come in handy, but you could just do
entry.Entity.CreatedOnUtc = DateTime.UtcNow;
I hope this will give you enough food for thought to figure out which option suits you best.

Related

The INSERT statement conflicted with the FOREIGN KEY constraint

I'm rather new to Entity Framework (code-first). Here are my two entities-
public class Employee
{
public Employee() { }
public long Id {get; set;}
public string Fullname {get; set;}
public virtual ICollection<Attendance> Attendances { get; set; }
}
public class Attendance
{
public Attendance() { }
public DateTime CheckinDateTime { get; set; }
public DateTime? CheckoutDateTime { get; set; }
public long EmployeeId { get; set; }
[ForeignKey("Id")]
public virtual Employee Employee{ get; set; }
}
Employee has one-to-many relation with Attendance.
I've tried to create a new Attendance data-
var attendance = new Attendance()
{ EmployeeId = 1,
CheckinDateTime = today.CurrentDateTime
};
DbContext.Attendances.Add(attendance);
DbContext.SaveChanges(); //Exception here.
I have an Employee record in database.
Why I'm getting the exception?
Code First enables you to describe a model by using C# or Visual Basic .NET classes. The basic shape of the model is detected by using conventions. Conventions are sets of rules that are used to automatically configure a conceptual model based on class definitions when working with Code First. The conventions are defined in the System.Data.Entity.ModelConfiguration.Conventions namespace.
You can further configure your model by using data annotations or the fluent API. Precedence is given to configuration through the fluent API followed by data annotations and then conventions. For more information see Data Annotations, Fluent API - Relationships, Fluent API - Types & Properties and Fluent API with VB.NET.
Here you find more about Entity Framework Code First Conventions
You set wrong ids name as FK and PK, you need add primary key for Attendance also,follow code first conventions name, change your model like:
public class Employee
{
public Employee()
{
Attendances = new List<Attendance>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long EmployeeId { get; set; }
public string Fullname { get; set; }
public virtual ICollection<Attendance> Attendances { get; set; }
}
public class Attendance
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long AttendanceId { get; set; }
public DateTime CheckinDateTime { get; set; }
public DateTime? CheckoutDateTime { get; set; }
[Required]
[ForeignKey("Employee")]
public long EmployeeId { get; set; }
public virtual Employee Employee { get; set; }
}
ForeignKey attribute is applied on Attendance navigation property to specify foreignkey property name for Attendance property.
Without DataAnnotation we can use Fluent API for configuration our relationship. Ofcourse you need use code first convention names
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//one-to-many
modelBuilder.Entity<Attendance>()
.HasRequired<Employee>(e => e.Employee) // Attendance entity requires Employee
.WithMany(a => a.Attendances); // Employee entity includes many Attendances entities
}
If your model not contains convention name, using Fluent API can use .HasForeignKey() and set specific name FK
public class Attendance
{
public long AttendanceId { get; set; }
public DateTime CheckinDateTime { get; set; }
public DateTime? CheckoutDateTime { get; set; }
//Not first code convention name
public long EmpId { get; set; }
public virtual Employee Employee { get; set; }
}
.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//one-to-many
modelBuilder.Entity<Attendance>()
.HasRequired<Employee>(e => e.Employee)
.WithMany(a => a.Attendances)
.HasForeignKey(e => e.EmpId);
}

Entity Framework: Why is a property of type array of objects not persisted to DB?

I have two entities where one has a one to many relationship to the other. Example:
public class Question
{
public int Id { get; set; }
public string Text { get; set; }
public Answer[] Answers { get; set; }
}
public class Answer
{
public int Id {get; set; }
public string Text { get; set; }
}
Using EF6 Code First I've setup this simple DbContext:
public class MyContext : DbContext
{
public MyContext()
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<Question> Questions { get; set; }
public DbSet<Answer> Answers { get; set; }
}
What I end up with in the DB are two similarly structured tables (int PK column and varchar column) and no representation of the "one Question has many Answers" relationship that I intended with the Question.Answers property.
Why doesn't EF Code First map the relationship and how can I fix this?
Entity Framework doesn't support mapping navigation properties of bare Array types. The property has to be of Type that implements the ICollection<T> interface in order to be mapped.
Try to change your code as follows:
public class Question
{
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
And when initializing Answers, set it to a HashSet or List:
this.Answers = new List<Answer>();

EF5, Inherited FK and cardinality

I have this class structure:
public class Activity
{
[Key]
public long ActivityId { get; set; }
public string ActivityName { get; set; }
public virtual HashSet<ActivityLogMessage> ActivityLogMessages { get; set; }
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
}
public abstract class LogMessage
{
[Required]
public string Message { get; set; }
public DateTimeOffset CreateDate { get; set; }
[Required]
public long ActivityId { get; set; }
public virtual Activity Activity { get; set; }
}
public class ActivityLogMessage : LogMessage
{
public long ActivityLogMessageId { get; set; }
}
public class FileImportLogMessage : ActivityLogMessage
{
public long? StageFileId { get; set; }
}
public class RowImportLogMessage : FileImportLogMessage
{
public long? StageFileRowId { get; set; }
}
Which gives me this, model
Each Message (Activity, File or Row) must have be associated with an Activity. Why does the 2nd and 3rd level not have the same cardinality as ActivityLogMessage ? My attempts at describing the foreign key relationship (fluent via modelbuilder) have also failed.
This is really an academic exercise for me to really understand how EF is mapping to relational, and this confuses me.
Regards,
Richard
EF infers a pair of navigation properties Activity.ActivityLogMessages and ActivityLogMessage.Activity with a foreign key property ActivityLogMessage.ActivityId which is not nullable, hence the relationships is defined as required.
The other two relationships are infered from the collections Activity.FileImportLogMessages and Activity.RowImportLogMessages. They neither have an inverse navigation property on the other side nor a foreign key property which will - by default - lead to optional relationships.
You possibly expect that LogMessage.Activity and LogMessage.ActivityId is used as inverse property for all three collections. But it does not work this way. EF cannot use the same navigation property in multiple relationships. Also your current model means that RowImportLogMessage for example has three relationships to Activity, not only one.
I believe you would be closer to what you want if you remove the collections:
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
You can still filter the remaining ActivityLogMessages by the derived types (for example in not mapped properties that have only a getter):
var fileImportLogMessages = ActivityLogMessages.OfType<FileImportLogMessage>();
// fileImportLogMessages will also contain entities of type RowImportLogMessage
var rowImportLogMessage = ActivityLogMessages.OfType<RowImportLogMessage>();

Creating relationships in CodeFirst EF which reference each other

I having some issues setting up the correct relationships that I require for a database.
In words...
I want to have a Firm which can multiple (zero:many) Branch
On the Firm I want to be able specify a HeadOffice which will exist will exist in the Branch table (zero:one)
What I expected to see is the Firm table to have the following fields: Id, Name, HeadOfficeId and on the Branches I expect to see: Id, TradingAs, Name, Firm_Id
Instead I see:
Firm table to have the following fields: Id, Name, HeadOfficeId
Branches I expect to see: Id, TradingAs, Name, Firm_Id, Firm_Id1
The model classes are as shown
public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Branch> FirmBranches { get; set; }
public virtual LawFirmBranch HeadOffice { get; set; }
}
public class Branch
{
public int Id { get; set; }
public string TradingAs {get;set;}
public string Name {get;set;}
public Firm Firm { get; set; }
}
I understand that this causes a circular reference type issue and I am happy to have the Firm HeadOffice to initially to be Null until there are values in the FirmBranches property.
Is there some way that I can specify that the HeadOffice has the null or one type relationship
If you're wanting to explicitly do this in your model, you can do this via data annotations:
public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Branch> FirmBranches { get; set; }
[ForeignKey("HeadOffice")]
public int? FirmId {get;set;}
public virtual LawFirmBranch HeadOffice { get; set; }
}
If you dont want this exposed in your code, you'll need to do this via fluent:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Firm>()
.HasOptional(f => f.HeadOffice)
.WithOptionalDependent();
}
This creates:
If you want to change the name of the key, you'd add a mapping to the end of the fluent map:
modelBuilder.Entity<Firm>()
.HasOptional(f => f.HeadOffice)
.WithOptionalDependent()
.Map(m => m.MapKey("YourKeyName"));

Entity Framework and Models with Simple Arrays

This is my model class.
public class Lead
{
private readonly ObservableCollection<String> m_tags = new ObservableCollection<string>();
public int LeadId { get; set; }
public string Title { get; set; }
public ObservableCollection<String> Tags { get { return m_tags; } }
}
Does Entity Framework offer a way to represent this using either Model-First or Code-First?
EDIT: I'm looking for a way to do this without changing the public API of the model. The fact that there is some sort of Tags table shouldn't be visible to the downstream developer.
Since your model has to be represented in a relational way, you can only use primitive types (that have an equivalent in a SQL DB) or other entities within a entity definition - that means the tags are represented by their own entity. In your case it would be something like this using Code first approach:
public class Lead
{
public int LeadId { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int TagId { get; set; }
public string Name { get; set; }
}
public class SomeContext : DbContext
{
public DbSet<Lead> Leads { get; set; }
public DbSet<Tag> Tags { get; set; }
}
This (by default) will be represented in the database as a table Leads, a table Tags, and a relationship table LeadTags that only contains {LeadId, TagId} pairs.