I have a problem, I have to create a model where we have two entities which CAN be linked together but can also exist as stand alone entities.
The model currently looks like this:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
public int? AuthorisationID{ get; set; }
}
The reason for this is that we can have an authorization record independent of a change, and some changes require authorisation and some dont, so neither side of the relationship is required.
I can configure this with the fluent API like so:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation);
And alls well, Except that the migration that it creates looks like this
CreateTable(
"dbo.Changes",
c => new
{
ChangeID = c.Int(nullable: false, identity: true),
AuthorisationID = c.Int(),
Authorisation_AuthorisationID = c.Int(),
})
.PrimaryKey(t => t.ChangeID)
.ForeignKey("dbo.Authorisations", t => t.Authorisation_AuthorisationID)
.Index(t => t.Authorisation_AuthorisationID);
EF is deciding that its going to add a new column (Authorisation_AuthorisationID) for me to use as the FK between the two entities, what I really want to be able to do is to use the change.AuthorisationID property as the FK onto the Authorisation, I cannot find a way to configure this at all (Please note that I need the FK to be in the model - for consistency with the rest of the app more than anything else).
To sum up I need to be able to create a relationship between two entities where both sides of the relationship are optional and if possible I want to be able to define the FK column myself.
Am I just approaching this wrong? Ive been staring at the same block of code for so long I could be missing something simple.
Looks like explicit foreign key property is not supported for one-to-one relationships - there is no HasForeignKey Fluent API and also if you put ForeignKey attribute on the navigation property you get exception saying that multiplicity must be *.
So the only choice you have is to remove the explicit Change.AuthorisationID property and work only with navigation properties:
Model:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
}
Configuration:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation)
.Map(a => a.MapKey("AuthorisationID"));
Related
I've worked with databases for a long time now but am new to Entity Framework. I handle both the aspects of programming and database development. As a db developer, I try to keep it clean so this structure that I came up with works well for me but I'm not sure if Entity Framework even supports it for I've tried for several days, using different scenarios, Data Annotations as well as Fluent API but couldn't get this to work.
What I'm trying to do might be a bit unconventional but what I'm trying to avoid is having to duplicate a file table for each area hence I define 1 file table that can be used by multiple areas using a Relationship. Thus, what I have is: one [company, employee, or project] can have many files (one to many). Similarly, the file table can be sourced by any area (many to many, in this case, it's not the data but rather the structure, hopefully that makes sense). The file records are related to only 1 area [company, employee, or project] (many to one).
The obvious advantage to this method is that I can avoiding having to manage 3 file tables but it doesn't end there. As you can see from the FileAccess table, instead of having multiple tables here or multiple fields to represent pointers to the multiple tables, I only need to manage 1 table for file access. The key is in the RelationTable and RelationId rather than the specific File.Id.
Below is a simplified example of the structure I'm trying to accomplish. Can it be done in Entity Framework?
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<File> Files { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<File> Files { get; set; }
}
public class Project
{
public int Id { get; set; }
public Guid? CompanyId { get; set; }
public string ProjectNo {get; set; }
public virtual ICollection<File> Files { get; set; }
}
public class File
{
public int Id { get; set; }
public Int16 RelationTable { get; set; } 0=Company, 1=Employee, 2=Project
public string RelationId { get; set; } Company.Id, Employee.Id, Project.Id
public string FileName { get; set; }
}
public class FileAccess
{
public int Id { get; set; }
public int EmployeeId { get; set; }
public Int16 RelationTable { get; set; } 0=Company, 1=Employee, 2=Project
public string RelationId { get; set; } Company.Id, Employee.Id, Project.Id
public string AccessType
}
As Ivan pointed out, EF doesn't support this due to the foreign key limitations but I was able to come up with a working solution. However, I must warn you that I'm only on my 3rd week of EF so I don't know what ramifications this may cause but this is what I did, for those who may be interested.
As it turns out (through trial and error), EF just needs the OnModelCreating to wire up the relationship between the objects, it doesn't really need the FK to be created thus I defined the relationship this way:
modelBuilder.Entity<File>()
.HasIndex(k => new { k.RelationTable, k.RelationId }); //for performance
modelBuilder.Entity<FileAccess>()
.HasMany(fa => fa.Files)
.WithOne(f => f.FileAccess)
.HasForeignKey(k => new { k.RelationTable, k.RelationId })
.HasPrincipalKey(k => new { k.RelationTable, k.RelationId });
//Using enumerations to control
relationships and adding PERSISTED so it doesn't need to be maintained plus it
won't break the Add-Migration with the "non persistent error"
modelBuilder.Entity<Project>()
.Property(f => f.RelationTable)
.HasComputedColumnSql((int)NTGE.Database.Shared.eFileRelTable.Projects + " PERSISTED") //This injects the value so we don't have to store it
.HasDefaultValue(123); //This doesn't really matter, we just need it so EF doesn't try to insert a value when saving, which will cause an error
modelBuilder.Entity<Project>()
.HasMany(p => p.Files)
.WithOne(f => f.Project)
.HasForeignKey(k => new { k.RelationTable, k.RelationId })
.HasPrincipalKey(k => new { k.RelationTable, k.Id });
When you add the above codes and run the Add-Migration, it'll cause it to add the below codes, which will break the Update-Database command so you'll need to comment it out in the Up function.
//migrationBuilder.AddForeignKey(
// name: "FK_Files_Projects_RelationTable_RelationId",
// table: "Files",
// columns: new[] { "RelationTable", "RelationId" },
// principalTable: "Projects",
// principalColumns: new[] { "RelationTable", "Id" },
// onDelete: ReferentialAction.Cascade);
//migrationBuilder.AddForeignKey(
// name: "FK_Files_FileAccess_RelationTable_RelationId",
// table: "Files",
// columns: new[] { "RelationTable", "RelationId" },
// principalTable: "FileAccess",
// principalColumns: new[] { "RelationTable", "RelationId" },
// onDelete: ReferentialAction.Cascade);
You'll need to do the same with the Down function else you won't be able to roll back your changes.
//migrationBuilder.DropForeignKey(
// name: "FK_Files_Projects_RelationTable_RelationId",
// table: "Files");
//migrationBuilder.DropForeignKey(
// name: "FK_Files_FileAccess_RelationTable_RelationId",
// table: "Files");
Now you can do an Update-Database and it should run just fine. Running the app works perfectly fine as well. I'm able to use the EF method to get the Project with the associated files and worked with the FileAccess object as well. However, keep in mind that this is a hack and future versions of EF might not support it. Cheers!
I use ASP.NET Core with Identity and want to extend default Db context. If I want to add not linked table I just add a new class:
public partial class Table1
{
public int Id { get; set; }
public string Txt { get; set; }
}
and extend my ApplicationDbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Table1> Table1 { get; set; }
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);
builder.Entity<Table1>(entity =>
{
entity.ToTable("Table_1");
entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.Txt)
.IsRequired()
.HasMaxLength(50);
});
}
}
then create a migration and update db. It works. But if I want to add a new table, which linked to table from IdentityDbContext:
public partial class Users
{
public int Id { get; set; }
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual AspNetUser User { get; set; }
}
of course, AspNetUser class does not exist (it's created by IdentityDbContext, as I understand). How to do it correctly?
The class is most likely named ApplicationUser (the default). The table that represents this entity is dbo.AspNetUsers, but that is set by Identity, and has nothing to do with the class name.
FWIW, though, it's a bad idea to create a Users entity, for a number of reasons:
There will undoubtedly be confusion between Users and ApplicationUser, as well as the database tables dbo.Users and dbo.AspNetUsers.
In general, you should name your entities in singular tense, i.e. User, not Users. There's a whole host of reasons for this convention, but suffice to say, it just makes your code better and more readable to stick to singular tense for singular things and plural tense for plural things. For example, a property of type ICollection<User> would be named Users, since it's composed of many User instances.
What you're doing is completely unnecessary. The whole reason for Identity's existence is that Membership (the previous authentication and authorization framework employed by ASP.NET) did not allow you to extend the types involved. Identity changes all this and is 100% extensible in every way. You have full access to all entities involved in the the framework and you can add to them and derive from them. If you want to add additional properties for "users" in your system, just add them to the ApplicationUser class directly.
I want to create a relationship between Users and Notifications which I think is a one-to-many relationship.
I'm having trouble with this and not sure how I am going wrong. Here is the code for the two classes:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public virtual ICollection<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Time { get; set; }
public string ApplicationUserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
I also have the following mapping on it:
modelBuilder.Entity<Notification>()
.HasRequired<ApplicationUser>(u => u.ApplicationUser)
.WithMany(u => u.Notifications)
.HasForeignKey(u => u.NotificationID);
I have came across different errors while trying to fix this such as:
Multiplicity is not valid
Multiplicity constraint violated. The role _ of the relationship _ has multiplicity 1 or 0..1
Edit:
The same exception (Multiplicity constraint violated. The role 'Notification_ApplicationUser_Target' of the relationship Notification_ApplicationUser' has multiplicity 1 or 0..1.) is thrown when I try to add notifications for all users as I have it done in this method:
public void AddNotification(Notification n)
{
var roleId = context.Roles.Where(m => m.Name == "User").Select(m => m.Id).SingleOrDefault();
var users = context.Users.Where(u => u.Roles.All(r => r.RoleId == roleId)).ToList();
try
{
foreach(var user in users)
{
user.Notifications.Add(n);
}
context.SaveChanges();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
I think the issue is coming from the string type of the foreign key. By default strings are nullable at the database level, but a required foreign key cannot be null. Specifying HasRequired only enforces that the relationship is required, not that the foreign key property must be not null. I think if you simply add [Required] to your foreign key property, that will correct the issue:
[Required]
public string ApplicationUserID { get; set; }
Obviously, you will need to do a migration to apply this change to your database.
EDIT
Sorry, I read into the exception wrong. The source of your problem is actually from adding the same notification to each user, as in literally the same notification. EF does object tracking, so after the entity instance referenced by n has been added once, it is being tracked by EF. Then, when you try to add it to another user, it thinks you're trying to do a many-to-many, basically, where that single Notification record will belong to multiple users.
Off the top of my head, I'm not sure what the best method is to fix this confusion in EF, but I have a couple of potential ideas:
You can try to detach the entity after adding it:
user.Notifications.Add(n);
context.Entry(n).State = EntityState.Detached;
However, I'm not sure if that will allow it to be added at all. You'll have to test. If it doesn't create it, you can also try saving before detaching. I think that would work, but it's obviously pretty inefficient committing to the database each iteration.
The safest method would be to create a new instance for each, and simply map over the data from n:
var notification = new Notification
{
Title = n.Title,
Description = n.Description,
Time = n.Time
};
user.Notifications.Add(notification);
That will ensure that every notification you add is a totally separate tracked instance.
Here is the proper configuration of your relationship:
modelBuilder.Entity<Notification>()
.HasRequired(u => u.ApplicationUser)
.WithMany(u => u.Notifications)
.HasForeignKey(u => u.ApplicationUserID);
In HasForeignKey method you need to specify the FK of that relationship, not the PK of Notification entity.
Update
The problem is you have an one to one relationship in your DB and you are trying to configure an one to many in your model. If this last one is what you really wants, then I suggest you to use Migrations to change your DB schema, otherwise you can configure your relationship this way:
modelBuilder.Entity<Notification>()
.HasRequired(u => u.ApplicationUser)
.WithOptional(u => u.Notification)
And change the navigation property in ApplicationUser entity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public virtual Notification Notification { get; set; }
}
I have a couple of classes (for this example anyway) that use code first with the entity framework to connect to the database.
public class Customer
{
[Key]
public long CustomerId { get; set; }
public string CompanyName { get; set; }
...
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public long ContactId { get; set; }
public string Forename { get; set; }
...
public long CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
When I hook these up in my context class directly to the db the foreign key relationships hook up fine and I can access the collection of contacts from within the customer class.
class RemoteServerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
...
}
My problem is that these database tables are used by various different systems and are massive. In order to increase efficiency I have overridden the default behaviour to point at a view (and also a stored proc elsewhere) rather than directly at the table.
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
public IEnumerable<Contact> Contacts()
{
return Database.SqlQuery<Contact>("SELECT * FROM vw_ContactsList");
}
I have made sure that in each of the views I have included the foreign key fields: CustomerId and ContactId.
When I do this however the class joins appear to be lost - there's always a null when I drill into either of the objects where it should be pointing to the other one. I have tried to set up what the foreign key field should point to but this doesn't seem to help either.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(p => p.Customer)
.WithMany()
.HasForeignKey(k => k.CustomerId);
}
Is there a way to establish the connection when overriding the default behaviour?
There is no overriding in this case. If you removed
public DbSet<Customer> Customers { get; set; }
and replaced it with
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
you have completely changed the behavior. The first uses entities and full power of EF. The second is only helper to execute custom SQL. Second without first or without defining entity in OnModelCreating doesn't use Customer as mapped entity at all - it uses it as any normal class (only mapped entities can use features like lazy loading).
Because your Customer is now mapped to view you cannot use your former Customer class used with table. You must define mapping of Customer to a view by cheating EF:
modelBuilder.Entity<Customer>().ToTable("vw_ContactsList"); // EF code fist has no view mapping
Once you have this you can try again using:
public DbSet<Customer> Customers { get; set; }
Unless your view is updatable you will get exception each time you try to add, update or delete any customer in this set. After mapping relation between Customer and Contact mapped to views your navigation properties should hopefully work.
The problem with SqlQuery is the way how it works. It returns detached entities. Detached entities are not connected to the context and they will not lazy load its navigation properties. You must manually attach each Customer instance back to context and to do that you again need DbSet.
I am using EF4 code first and want to generate a composite key which is made of a class property and foreign key. I have two classes: Order and Company. The Order class holds a reference but this will not necessarily be unique between companies. So I intend to use a composite key made up of Reference and Company.CompanyId.
I have tried using the following to set it but I get an error message "Key expression is not valid".
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.Company.CompanyId });
I have also tried
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.Company });
and this fails.
these are my classes:
public class Order
{
public string Reference { get; set; }
public Company Company { get; set; }
}
public class Company
{
public int CompanyId { get; set; }
public virtual ICollection Orders { get; set; }
}
Any help would be greatly appreciated.
As Antony Highsky mentioned, you can only use scalar properties in the key.
So, you will need to add a foreign key (scalar property) to the Order class and associate it with the navigation property Company as shown below:
public class Order
{
public string Reference { get; set; }
public int CompanyId { get; set; }
[RelatedTo(ForeignKey = "CompanyId")]
public Company Company { get; set; }
}
And then create the composite key using the model builder:
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.CompanyId });
Note that data annotations (RelatedTo attribute) were introduced with the Entity Framework CTP 3. For another option that only uses data annotations instead of HasKey method, see this post:
http://www.luisrocha.net/2010/11/creating-composite-keys-using-code.html
One thing that doesn't look quite right is your use of the non-generic version of ICollection. Try this:
public virtual ICollection<Order> Orders { get; set; }
Did you try this?
modelBuilder.Entity().HasKey(o =>o.Reference );
modelBuilder.Entity().HasKey(o =>o.CompanyId );
According to this source, only scalar properties are allowed in the key. Navigation properties are not.