I want to implement a change log as advised in
Dev Express XAF T474899
I am using the security system generated by the XAF new solution wizard
I have defined some business objects to store the change log information.
One of these objects stores a link to the user
public virtual User User { get; set; }
On generating the code migration I am surprised to see the Up() method add the following
RenameTable(name: "dbo.UserRoles", newName: "RoleUsers");
DropPrimaryKey("dbo.RoleUsers");
AddPrimaryKey("dbo.RoleUsers", new[] { "Role_ID", "User_ID" });
On another occasion I found the following in an Up()
RenameTable(name: "dbo.EventResources", newName: "ResourceEvents");
// lots of other stuff
DropPrimaryKey("dbo.ResourceEvents");
AddPrimaryKey("dbo.ResourceEvents", new[] { "Resource_Key", "Event_ID" });
On both occasions the code that creates the entities is a Dev Express libary.
I have cross posted this question to Dev Express Support
The Dev Express business objects are defined in DevExpress.Persistent.BaseImpl.EF;
My DbContext context refers to them as
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
The meta data for Role shows
The meta data for User shows
My own business classes contain
namespace SBD.JobTalk.Module.BusinessObjects
{
[NavigationItem("Configuration")]
[DisplayName("Staff")]
[DefaultProperty("Summary")]
[ImageName("BO_Employee")]
[Table("Staff")]
public class Staff : BasicBo
{
public Staff()
{
Person = new Person();
}
public virtual Person Person { get; set; }
[StringLength(100, ErrorMessage = "The field cannot exceed 100 characters. ")]
[scds.Index("IX_Staff_UserName", 1, IsUnique = true)]
public string UserName { get; set; }
[NotMapped]
public string Summary => $"{Person.FirstName} {Person.LastName}";
//public virtual User User { get; set; }
}
}
public abstract class BasicBo : IXafEntityObject
{
[Browsable(false)]
[Key]
public virtual int Id { get; set; }
public virtual void OnCreated()
{
}
public virtual void OnSaving()
{
}
public virtual void OnLoaded()
{
}
}
If I un-comment the code to have the User property inside Staff, and generate a migration, the migration Up is
public override void Up()
{
RenameTable(name: "dbo.UserRoles", newName: "RoleUsers");
DropPrimaryKey("dbo.RoleUsers");
AddColumn("dbo.Staff", "User_ID", c => c.Int());
AddPrimaryKey("dbo.RoleUsers", new[] { "Role_ID", "User_ID" });
CreateIndex("dbo.Staff", "User_ID");
AddForeignKey("dbo.Staff", "User_ID", "dbo.Users", "ID");
}
[Update]
Interestingly there are more Dev Express tables than I first thought.
The primary keys are Identity.
I think am using Standard Authentication created before Dev Express added the Allow/Deny ability (V16.1)
[Update]
When I create a new project with the above settings, here is the DbContext.
using System;
using System.Data;
using System.Linq;
using System.Data.Entity;
using System.Data.Common;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.ComponentModel;
using DevExpress.ExpressApp.EF.Updating;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
namespace XafApplication1.Module.BusinessObjects {
public class XafApplication1DbContext : DbContext {
public XafApplication1DbContext(String connectionString)
: base(connectionString) {
}
public XafApplication1DbContext(DbConnection connection)
: base(connection, false) {
}
public XafApplication1DbContext()
: base("name=ConnectionString") {
}
public DbSet<ModuleInfo> ModulesInfo { get; set; }
public DbSet<PermissionPolicyRole> Roles { get; set; }
public DbSet<PermissionPolicyTypePermissionObject> TypePermissionObjects { get; set; }
public DbSet<PermissionPolicyUser> Users { get; set; }
public DbSet<ModelDifference> ModelDifferences { get; set; }
public DbSet<ModelDifferenceAspect> ModelDifferenceAspects { get; set; }
}
}
OK, I will take a stab :) Your Up() code is trying to rename the table UserRoles to RoleUsers. This means you have a prior migration where UserRoles was the table name - probably from your DevEx stuff. This could happen if they changed their models in an upgrade. The current models are expecting RoleUsers etc. so you need to get there.
So first option is let the migration do the renaming to match the underlying model. I assume this didn't work or causes other issues?
You might be able to 'fool' entity framework into using the old tables with fluent code or annotations, but if it has new columns or relationships that won't work.
What I would do is this:
1) Create a new test project with the same references you had and
copy your context and DbSets. Point the connection string to a
new database.
2) Add a migration and script it out:
update-database -Script.
3) Examine this script a use it to create
the objects needed in your database. Migrate data from the old
tables to new if needed.
4) Remove the old tables
5) In your actual
project add a migration to resync your models:
add-migration SyncDevExUpdate -IgnoreChange, update-database
Now you will have the tables your models expect.
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'm creating initial migration using
Add-Migration InitialCreate
But then when I'm updating my database tables from IdentityDbContext are not created so I get exceptions.
So how do I create migration for AspNetUser tables from IdentityDbContext?
Regards teamol
You can add custom fields to your AspNetUser table in your IdentityModels.cs file.
First add your custom values ito ApplicationUser class in IdentityModels:
namespace YourProjectName.Models
{
public class ApplicationUser : IdentityUser
{
public string Email { get; set; }
public string NameSurname { get; set; }
public string ProfilePhotoRoute { get; set; }
public string Title { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
After that, enter "Add-Migration NewMigration" command in package manager console.
Finally, enter "Update-Database" command in package manager console.
If your connection string -which is stated in web.config- is true, you can update succesfully your database with this way.
I'm trying to go through this tutorial on the External Authentication Services (C#). I need some initial explanations to go forward. Looking inside the default template that ships MVC5, I see this:
// You can add profile data for the user ...
public class ApplicationUser : IdentityUser
{
public string HomeTown { get; set; }
public DateTime? BirthDate { get; set; }
}
What if I want to call it User instead of ApplicationUser? Maybe I want to add a navigation properties so I can establish relationship with my other models? Also when I look at the table, the name is AspNetUsers instead of ApplicationUser. Finally, What if I want to use my own context?
Thanks for helping.
1)What if I want to call it User instead of ApplicationUser?
You can change to any names you want, just make sure to replace ApplicationUser with the name.
2)Maybe I want to add a navigation properties so I can establish
relationship with my other models?
If you have a class call Address, you want to add addressId as a foreign key, see below:
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
}
public class User : IdentityUser
{
public string HomeTown { get; set; }
public DateTime? BirthDate { get; set; }
public int AddressId { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<User>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public DbSet<Address> Addresses { get; set; }
}
3)Also when I look at the table, the name is AspNetUsers instead of
ApplicationUser.
ASP.NET Identity is inherited from the The ASP.NET membership system. When you register a new user
using the default template, AspNetUsers and AspNetUserRoles etc.. these tables are created by default.
You can modify these table name by modifying the IdentityModel.cs. For more detail take a look at the following link:
How can I change the table names when using Visual Studio 2013 ASP.NET Identity?
4)What if I want to use my own context?
You can create your own DBContex, MVC 5 allow you to have mutiple DBContext, such as ApplicationDbContext and DataDbContext(custom DbContext).
ApplicationDbContext usually contains ASP.NET Membership data table.
DataDbContext usually contains data tables unrelated to users.
public class Blog
{
public int BlogId { get; set; }
public int Title { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext()
: base("DefaultConnection")
{
}
public DbSet<Blog> Blogs { get; set; }
}
Note: You may need to use EF Migrations, see details here :
ASP.Net Identity customizing UserProfile
In my project I use MVC4 and some external database using Entity as orm.
I decided to use membership given by MVC, so I just changed in default ConnectionString to point to my external db.
Then, when I launch the app first time, few tables were added, so far so good. Now, the problem is , that when I map new created userProfile table into my dataContext model, then I have a conflict, because this table allready exists in accountModel.
Account model and my new generated model are in the same namespace, which I don't wanna change, so what can I do?
here is class generate by ADO entity model using view add tables method:
public partial class UserProfile
{
public UserProfile()
{
this.Predictions = new HashSet<Prediction>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Prediction> Predictions { get; set; }
}
and here from membership
[Table("UserProfile")]
public partial class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
both exist in the same name space, and are in conflict.
You can remove the auto generated class and create a partial class to extend the AccountModel UserProfile class instead.
Create a partial class with the same name and same namespace:
public partial class UserProfile
{
public UserProfile()
{
this.Predictions = new HashSet<Prediction>();
}
public virtual ICollection<Prediction> Predictions { get; set; }
}
By doing this, you have the Predictions property into the membership UserProfile class.
I have a State model class:
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
}
And I am trying to create a Repository:
Scaffold Repository State
I've got in generated file:
public IQueryable<State> All
{
get { return context.State; }
}
instead of context.StateS.
Property
public DbSet<State> States { get; set; }
successfully has been added to the DbContext class.
I have no overrided OnModelCreating method.
Sometimes I mention such problem in different projects but can not find a reason.
I know I've had problems with namespace confusion when using the word "State" for my database tables and POCOs. So to make things easier, I rename those to something else, like USState or StateCode. That could be what's going on here for you and the scaffolding.