We are using Entity Framework Code First for this project's database.
Our requirements call for a central 'Resource' table, with a single column of ResourceId (uniqueidentifier NOT NULL DEFAULT (newsequentialid())).
Various tables would use this table for their ID.
Profile - ProfileId (uniqueidentifier NOT NULL)
Organization - OrganizationId (uniqueidentifier NOT NULL)
Document = DocumentId (uniqueidentifier NOT NULL)
So, if I create a new Profile record, I would create a new Resource record, and use that sequentially created Guid as the ID for the new Profile record.
The reason for this is to prevent an Id from Profile ever being present as an Id for Organization. (I know that this is most likely improbable, but not impossible.)
Right now we define this with relationships like this:
public class Resource : BaseEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ResourceId { get; set; }
public virtual Profile Profile_ProfileId { get; set; }
//...
}
public class Profile : BaseEntity, IAuditableEntity
{
[Key]
public Guid ProfileId { get; set; }
public virtual Resource Resource { get; set; }
//...
}
public class ProfileMapping : EntityTypeConfiguration<Profile>
{
public ProfileMapping()
{
//Primary key
HasKey(t => t.ProfileId);
//Constraints
Property(t => t.ProfileId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
//...
ToTable("Profile");
//Create Relation
HasRequired(t => t.Resource).WithOptional(t => t.Profile_ProfileId);
}
}
Then, when we create a new Profile we do this (db being an instance of our DBContext):
var res = new Resource();
db.Resource.Add(res);
var newProfile = new Profile{
ProfileId = res.ResourceId,
IsActive = true
};
db.Profile.Add(newProfile);
However, I am wondering, could we define our classes/models to inherit from Resource and get better results?
Have any of you worked with a database structure like this?
Actually, since the GUIDs for ProfileId and OrganizationId are generated on the same database server, you have a 100% guarantee that they are unique. I am assuming that you will let the database server generate the GUIDs.
GUIDs might have a chance (a very small chance) to collide if they are generated on different machines.
Anyway, here is a direct answer to your question:
You can do something like this:
public class Resource
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ResourceId { get; set; }
}
public class Profile
{
[Key]
[ForeignKey("Resource")]
public Guid ProfileId { get; set; }
public Resource Resource { get; set; }
public string Name { get; set; }
public Profile()
{
Resource = new Resource();
}
}
Note how the Profile entity is creating a Resource entity in its constructor. Also note that the primary key for Profile is also a foreign key.
UPDATE:
Here is another solution that I think is better, and that will work also in the case where you want to access the Profile entity from the Resource entity:
I added a Profile property to the Resource entity:
public class Resource
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ResourceId { get; set; }
public virtual Profile Profile { get; set; }
}
Here is the profile entity:
public class Profile
{
[Key, ForeignKey("Resource"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid ProfileId { get; set; }
public Resource Resource { get; set; }
public string Name { get; set; }
}
Notice that I no longer create a Resource object in the constructor.
Instead, I create it whenever the entity is saved by overriding the SaveChanges method on the DbContext like this:
public class MyContext : DbContext
{
public DbSet<Resource> Resources { get; set; }
public DbSet<Profile> Profiles { get; set; }
public override int SaveChanges()
{
foreach (var profile in ChangeTracker.Entries<Profile>()
.Where(x => x.State == EntityState.Added))
{
profile.Entity.Resource = new Resource();
}
//Here you also need to do the same thing for other Entities that need a row in the Resources table (e.g. Organizations)
return base.SaveChanges();
}
}
Related
I am using Entity Framework code first.
I have multiple classes that required an audit trail (e.g. Car, Van).
When a change is made to an instance of this class, the audit trial is updated.
These classes all inherit from a parent (Vehicle) and they all use a GUID as an ID.
My Audit Trail class has a reference to this GUID and an audit message.
How do I configure my domain objects so that when I delete a Car, all of the corresponding Audit Trail items are deleted?
Is there a way to do this in the domain model, do I need to configure this elsewhere, or should I just be cleaning up the Audit Trail repository after every delete operation?
public class Car : Vehicle
{
public string CarProperty { get; set; }
}
public class Vehicle
{
public Guid Id { get; set; } = Guid.NewGuid();
public string ItemName { get; set; }
}
public class AuditTrail
{
public Guid Id { get; set; } = Guid.NewGuid();
public string AuditNote { get; set; }
public Guid VehicleId { get; set; }
}
You have two options:
You can keep you existing AuditTrail class where VehicleId points to either a Car or a Van etc. but without any Foreign key constraint since it will be able to point towards multiple tables in your data base (you will probably want to record which table the AuditTrail is for). This is tidy in that you won't have too many fields, but means that you will need to write code to delete AuditTrails when you delete the corresponding Vehicle.
You can create a separate field for each Table which relates to the AuditTrail (CarId, VanId etc.) and then create a Foreign Key constraint for each relationship with a cascade delete rule which will automatically delete the AuditTrail with the corresponding vehicle. This will mean adding a new field and constraint for each new Vehicle Table you add and creating code to handle all the different types when creating the AuditTrail.
public class Car : Vehicle
{
public string CarProperty { get; set; }
public IList<AuditTrail> AuditTrails { get; set; }
}
public class Vehicle
{
public Guid Id { get; set; } = Guid.NewGuid();
public string ItemName { get; set; }
}
public class AuditTrail
{
public Guid Id { get; set; } = Guid.NewGuid();
public string AuditNote { get; set; }
[ForeignKey(nameof(CarId))]
public Car Car { get; set; }
public Guid? CarId { get; set; }
}
I'm new to Identity Framework and maybe what I'm doing here is not the best approach, but either way, here is my scenario:
Beside the "Role" factor, some areas on my application should consider also if a given user is attached to a given "Company".
I created a new entity "Company" (very simple, only with Id and Name) and a relationship entity "UserCompany" (with the user and the company's Id). I tried to make it as similar as possible with the structure used between Roles and Users on Identity Framework.
In my ApplicationDbContext I added both DbSets and some logic for, for example, adding a list of companies to a user.
The problem that I'm facing is that "SaveChanges" does not apply the changes to the database.
*Edit: no error is thrown and the result of SaveChanges() is "1".
Here is a sample code from my ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Company> Companies { get; set; }
public DbSet<UserCompany> UserCompanies { get; set; }
//constructors, etc...
public void AddToCompanies(string _userId, params string[] _companyIds)
{
foreach (var companyId in _companyIds)
{
UserCompanies.Add(new UserCompany()
{
UserId = _userId,
DataAreaId = companyId
});
}
int result = this.SaveChanges();
}
}
And here is how I mapped this "UserCompany" entity
public class UserCompany
{
[Key, Column(Order = 1), ForeignKey("ApplicationUser")]
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
[Key, Column(Order = 2), ForeignKey("Company")]
public string DataAreaId { get; set; }
public virtual Company Company { get; set; }
}
On my UserAdminController class I created a private ApplicationDbContext object that is responsible for calling this logic. I suspect there is some problem in the way I'm dealing with two diferent contexts to save this data (one inside the ApplicationUserManager object and this new one), but I'm not sure if this is really the problem or if I'm missing something else here.
Any help would be appreciated!
Is the UserCompany model supposed to be like this
public class UserCompany
{
[Key, Column(Order = 1)]
public string UserId { get; set; }
[ForeignKey("UserId ")]
public virtual ApplicationUser ApplicationUser { get; set; }
[Key, Column(Order = 2)]
public string DataAreaId { get; set; }
[ForeignKey("DataAreaId")]
public virtual Company Company { get; set; }
}
And check if you use TransactionScope and not commit it.
Hope it helps!
I have a read-only view (dbo.HRINFO). I built the below object to represent it. I also have a "User" table that contains a FK to this view, though no official constraint can be defined. I'm trying to establish a navigation property so that whenever I have a "User" object, I can access the "UserInfo/view" data.
[Table("dbo.HRINFO")]
public class UserInfo
{
[Key] // But it's not really a key, there is no key, but it is null or unique
public string EMAIL_KEY { get; set; }
... more properties
}
And a table I'm creating that I want to related to the above view:
public class User
{
[Key]
public int Id { get; set; }
public string Email_Key { get; set; } // can be a FK to above view
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual UserInfo UserInfo { get; set; }
... more properties
}
I tried using the fluent API to no avail (the below, as well as many variations):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserInfo>().HasKey(t => t.EMAIL_KEY).ToTable("dbo.HRINFO");
modelBuilder.Entity<User>().HasKey(t => t.Id).HasRequired(t => t.UserInfo);
}
I'm at the point that I'm not what I want to do can be done, since I can't add any constraints to the view. Can I instead put a public virtual UserInfo { get { [sql select * from the view where EMAIL_KEY = this.Email_Key] } } on the User object? Or something similar? How?
This is the best I've come up with. I don't like the new context/inner select. I'm sure it will come back to bite me in the future. I'd welcome other ideas.
public class User
{
[Key]
public int Id { get; set; }
public string Email_Key { get; set; }
[NotMapped]
public virtual UserInfo UserInfo
{
get
{
return new Context().UserInfoes.Single(u => u.EMAIL_KEY == Email_Key);
}
}
}
I am trying to form a relationship of 2 tables to a 3rd, on a 1 to many basis. I have the following code:
public class CompanyInvolvement
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
}
public class Person
{
public Person()
{
CompanyInvolvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
public virtual ICollection<CompanyInvolvement> CompanyInvolvements { get; set; }
}
public class Company
{
public Company()
{
Involvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
[Required]
public string CompanyName { get; set; }
public virtual ICollection<CompanyInvolvement> Involvements { get; set; }
}
So effectively a Person can have many involvements in companies and a Company can have many people involved with it. The model builder is used like so:
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Person).WithMany(x => x.CompanyInvolvements);
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Company).WithMany(x => x.Involvements);
I originally created the relationship using the modelbuilder, specifying left and right keys (CompanyId and PersonId) and this worked great. But now I need the Start and End dates for an Involvement, I guess I needed to create a dedicated entity.
The question: When I use the above structure, I can create and read out involvements for a company and also see involvements for a Person. However, when I try to do the following:
var person = _context.People.FirstOrDefault(x => x.Id == personId);
var involvement = company.Involvements.FirstOrDefault(x => x.Person == person );
company.Involvements.Remove(involvement);
_context.SaveChanges();
I get the following error:
A relationship from the 'CompanyInvolvement_Company' AssociationSet is
in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'CompanyInvolvement_Company_Source' must also in the
'Deleted' state.
I think my virtual properties in the 3 entities are correct, but I have the feeling the modelbuilder logic I have may be slightly misconfigured?
I finally figured out what I was doing wrong. I needed to remove the Id property from the CompanyInvolvement entity and add the following composite key:
[Key, Column(Order = 0)]
public Guid PersonId { get; set; }
[Key, Column(Order = 1)]
public Guid CompanyId { get; set; }
I'm guessing by convention, these two properties were then linked as foreign keys to the Person and Company entities respectively. I also removed the modelbuilder mapping as stated in my original question. Once these were done, deleting CompanyInvolvements worked as expected.
I am using the entity framework (code first).
I would like to know if I really need to use a property with Id for relationship with another entity as in the code below.
public class User
{
public int Id { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public int ProfileId { get; set; }
public Profile Profile{ get; set; }
}
public class Profile
{
public int Id { get; set; }
public string Description{ get; set; }
}
For this way when I insert a user by setting the profileid property performs perfectly.
But when I don't use the profileid property in the Profile class,
public class User
{
public int Id { get; set; }
public string Login { get; set; }
public string Password { get; set; }
public Profile Profile{ get; set; }
}
public class Profile
{
public int Id { get; set; }
public string Description{ get; set; }
}
the execution the insert method adds another profile record. Why?
My mapping:
public class EntityMapping<Entity> : EntityTypeConfiguration<Entity> where Entity : EntityBase
{
public EntityMapping()
{
HasKey(e => e.Id);
Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class UserMapping : EntityMapping<User>
{
public UserMapping() : base()
{
ToTable("USER");
Property(p => p.Id).HasColumnName("USER_CD_USER");
Property(p => p.Login).HasColumnName("USER_TX_LOGIN").HasMaxLength(10).IsRequired();
Property(p => p.Password).HasColumnName("USUA_TX_PASSWORD").HasMaxLength(8).IsRequired();
HasRequired(e => e.Profile).WithMany(p => p.Users).Map(p => p.MapKey("PROF_CD_PROFILE"));
}
}
public class ProfilelMapping : EntityMapping<Profile>
{
public ProfileMapping()
: base()
{
ToTable("PROFILE");
Property(p => p.Id).HasColumnName("PROF_CD_PROFILE");
Property(p => p.Description).HasColumnName("PROFILE_DS_PROFILE").HasMaxLength(20).IsRequired();
HasMany(e => e.Users).WithRequired(p => p.Profile);
}
}
You are asking two questions.
Do I need to use FK property?
No you don't but EF behavior changes if you use it or not. More about it is in separate answer and linked blog article.
Why EF inserts Profile again?
Creating relations with existing entities requires special care. EF doesn't check if your entity exists in the database - you must tell it to EF. Here is one of many ways how to achieve that (without loading profile from the database):
var user = GetNewUserSomewhere();
context.Users.Add(user);
// Dummy profile representing existing one.
var profile = new Profile() { Id = 1 };
// Informing context about existing profile.
context.Profiles.Attach(profile);
// Creating relation between new user and existing profile
user.Profile = profile;
context.SaveChanges();
Short answer: Yes. It's the way EF work. It needs to store the foreign key in a dedicated property. Have you ever generated the class structure from a database? It always adds that key property. There are cases you don't need the Profile property loaded, but later you might want to retrieve it. That's what the dedicated ProfileId property is used, it will read the key value from there and load the object.