I am Using EF 6.0 and want to combine AppDbContext & IdentityDbContext into a single context which is AppDbContext.
This is a requirement as i have other tables which has relations with the AspNetUsers table created by EF.
The problem is EF is creating two tables for Users such as AspNetUsers and IdentityUsers.
Also If i use DbSet<ApplicationUser> in DbContext instead of DbSet<IdentityUsers> , Then add-migration throws up error.
My AppDbContext is
public class AppDbContext : IdentityDbContext<ApplicationUser>,IDisposable
{
public AppDbContext()
: base("MvcArchBSEFDB", throwIfV1Schema: false)
{
}
public static AppDbContext Create()
{
return new AppDbContext();
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
//public DbSet<ApplicationUser> AppUsers { get; set; } // breaks migration
//Multiple object sets per type are not supported. The object sets 'AppUsers ' and 'Users' can both contain instances of type 'MvcArchBS.DAL.Setp.ApplicationUser'.
public DbSet<IdentityUser> AppUsers { get; set; } // Creates Two Tables AspNetUsers & IdentityUser
public DbSet<IdentityUserRole> UserRoles { get; set; }
public DbSet<IdentityUserClaim> Claims { get; set; }
public DbSet<IdentityUserLogin> Logins { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<SubModule> SubModules { get; set; }
public DbSet<PageMst> Pages { get; set; }
public void Dispose()
{
base.Dispose();
}
}
And My ApplicationUser Class is
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
status = "A";
reptngusrid = "admin";
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int usrid { get; set; }
public string usrdescr { get; set; }
public int empid { get; set; }
public int usrgrpid { get; set; }
[StringLength(1)]
public string status { get; set; }
public string reptngusrid { get; set; }
public int defmodid { get; set; }
[StringLength(20)]
public string cltur { get; set; }
[ForeignKey("defmodid")]
public virtual Module Module { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
How do i get this to work ? I want to use something like context.AppUsers in my queries which i am unable to get.
It turns out my Service Layer Project also needs a reference to Microsoft.AspNet.Identity.EntityFramework.dll.
Once i added that all identity entity sets are available now with my context.
Also as tmg & Brendan had pointed out the IdentityDbContext members DbSets are not required in the AppDbContext.
Hope this helps someone.
Related
I have a many-to-many relationship between AppUser : IdentityUser, and TaxAccount, which is an abstract class that separates into Household and Business. I want to query how many TaxAccounts a User has access to on load.
Here's the AppUser class.
public class AppUser : IdentityUser
{
public string FullName { get; set; }
public virtual List<TaxAccount> Accounts { get; set; }
}
Here's the TaxAccount classes.
public abstract class TaxAccount
{
[Key]
public int ID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public virtual List<AppUser> Users { get; set; }
}
public class Household : TaxAccount
{
[MaxLength(1000)]
public string HomeAddress { get; set; }
}
public class Business : TaxAccount
{
[EmailAddress, MaxLength(500)]
public string Email { get; set; }
}
However, when I attempt to query the AppUser object in my Razor page, AppUser.Accounts is null! As a result, I always return to the "NoAccount" page.
public async Task<IActionResult> OnGetAsync()
{
AppUser = await _manager.GetUserAsync(User);
// Checks how many TaxAccounts the user has.
if (AppUser.Accounts == null)
{
return RedirectToPage("NoAccount");
}
else if (AppUser.Accounts.Count == 1)
{
return RedirectToPage("Account", new { accountID = AppUser.Accounts[0].ID });
}
else
{
return Page();
}
}
I've found that if I use a .Include() statement to manually connect TaxAccounts to AppUsers, the connection sticks! I just have to insert a .ToList() call that goes nowhere. My question is: why do I need this statement?
AppUser = await _manager.GetUserAsync(User);
_context.Users.Include(X => X.Accounts).ToList();
// Now this works!
if (AppUser.Accounts == null)
EDIT: I've tested removing virtual to see if it was a lazy loading thing, there's no difference.
I am making a web app using blazer and identity framework. I would like the object BoardYear to have a one-to-one relation with Member as chairman which is an IdentityUser class and selected by a dropdown.
When I try to insert a new BoardYear with an exciting Member as chairman I get the following SQL exception.
SqlException: Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is (de20c079-ed99-4bf9-9474-d8eb1a05b5b6).
It seems like Entity Framework wants to add the referred Member to the database, which is not possible because it already exists.
The member class:
public class Member : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid? ChairmanBoardYearId { get; set; }
public BoardYear ChairmanBoardYear { get; set; }
...
}
Boardyear class:
public class BoardYear
{
[Key]
public Guid BoardYearId { get; set; }
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
public DateTime EndDate { get; set; }
public Member Chairman { get; set; }
}
The relation is configured in the dbcontext like this:
public class ApplicationDbContext : IdentityDbContext<Member>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<BoardYear>()
.HasOne(m => m.Chairman)
.WithOne(b => b.ChairmanBoardYear)
.HasForeignKey<Member>(m => m.ChairmanBoardYearId);
base.OnModelCreating(builder);
}
public DbSet<BoardYear> BoardYears { get; set; }
public DbSet<Commission> Commissions { get; set; }
public DbSet<CommissionFine> CommissionFines { get; set; }
public DbSet<MemberFine> MemberFines { get; set; }
public DbSet<Meeting> Meetings { get; set; }
public DbSet<MeetingFile> MeetingFiles { get; set; }
public DbSet<MemberSanction> MemberSanctions { get; set; }
public DbSet<CommissionSanction> CommissionSanctions { get; set; }
public DbSet<YearGroup> YearGroups { get; set; }
public DbSet<CommissionList> CommissionLists { get; set; }
}
When the dropdown is edited the chairman is assigned like this in the razor page (I can see it gets the correct member):
async void OnBoardUserChange()
{
Member selectedMember = await MemberService.FindMember(selectedChairmanId);
curBoardYear.Chairman = selectedMember;
}
And then put in the database in the following service:
public class BoardYearService
{
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
private readonly DidaDbContext _db;
public BoardYearService(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<int> Create(BoardYear boardYear)
{
using (var ctx = _contextFactory.CreateDbContext())
{
CommissionList boardYearCommissionList = new CommissionList();
boardYear.CommissionList = boardYearCommissionList;
ctx.BoardYears.Add(boardYear);
return await ctx.SaveChangesAsync();
}
}
}
The problem is that you have retrieved the user from whatever context instance MemberService.FindMember uses, but are referencing it in a BoardYear added to a new context instance.
The second context instance, where the BoardYear is added is not tracking the Member, so assumes it is an addition.
Either perform the whole action using data retrieved and persisted back through the same context instance, or just use an Id field on the BoardYear instead of a navigation property.
I am new to ASP.NET Core and I am trying to setup basic relationship with EntityFramework Core. I have a NavigationCategory class that has 1:M relationship with WebPage class. The relationship is understood by my DB (It has a foreign key) but when retrieving the WebPage from repository, it has no NavigationCategory, even though it has NavigationCategoryId set.
public class WebPage : BaseEntity
{
private string _route;
public string Title { get; set; }
public string Body { get; set; }
[ForeignKey("NavigationCategory")]
public long NavigationCategoryId { get; set; }
public NavigationCategory NavigationCategory { get; set; }
public WebPage()
{
}
}
public class NavigationCategory : BaseEntity
{
public string Title { get; set; }
public IEnumerable<WebPage> WebPages { get; set; }
public NavigationCategory()
{
}
}
This is simple BaseEntity:
public class BaseEntity
{
public long Id { get; set; }
}
This is my DB context:
public class AppDataContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<KinderClass> KinderClasses { get; set; }
public DbSet<Feed> Feeds { get; set; }
public DbSet<NavigationCategory> NavigationCategories { get; set; }
public DbSet<WebPage> WebPages { get; set; }
public AppDataContext(DbContextOptions<AppDataContext> options) : base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<WebPage>()
.HasOne(wp => wp.NavigationCategory)
.WithMany(c => c.WebPages)
.HasForeignKey(wp => wp.NavigationCategoryId);
}
}
You need to explicitly include any navigation property you want included when you fetch entities using EF Core. For example, update your query as follows:
var webpage = dbContext.WebPages
.Include(w => w.NavigationCategory)
.FirstOrDefault();
Note, you need these two namespaces, at minimum:
using Microsoft.EntityFrameworkCore;
using System.Linq;
Learn more about loading related data and why lazy loading shouldn't be used in web apps.
I work on a MVC 5 project with EF6.1. As a beginner, I try to keep the code generated by the MVC 5 template in order to use the default authentification system with AccountController.
However, I had to add some classes, like "User.cs" as below, so I'm using Entity Framework Data Model.
public partial class User
{
public User()
{
this.Logs= new HashSet<Log>();
}
public int IdUser { get; set; }
public string AspNetUsersId { get; set; } //References to AspNetUsers.Id
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job{ get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
So, I have two contexts :
public partial class MyEFDataModelContainer : DbContext
{
public MyEFDataModelContainer ()
: base("name=MyEFDataModelContainer")
{
}
public DbSet<User> Users { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
So, my scenario is that I need to create an AspNetUsers, then add a AspNetRoles to this AspNetUsers and then create a User. We suppose the AspNetUsers creation and AspNetRoles works, but if the User creation fails, how can I rollback the AspNetUsers and AspNetRoles?
Can I create a transaction with these two contexts?
Unfortunately, I also need to call WCF webservices to execute this task? What can I do?
Best regards,
Updated
My merged contexts:
public partial class User: IdentityUser { }
public partial class MyEFDataModelContainer : IdentityDbContext<User>
{
public MyEFDataModelContainer() : base("name=MyEFDataModelContainer")
{ }
}
My AccountController.cs:
public class AccountController : Controller
{
public AccountController()
{
UserManager =new UserManager<User>(new UserStore<User>(new MyEFDataModelContainer()))
}
public UserManager<User> UserManager {get;set;}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new USER() { UserName = model.UserName, Email = model.Email, PhoneNumber = model.Phone };
var result = await UserManager.CreateAsync(user, model.Password);
var role = await UserManager.AddToRoleAsync(user.Id, "Membre");
if (result.Succeeded)
{
using (UserBusiness business = new UserBusiness())
{
var userResult = business.AddUser(model.FirstName, model.LastName, user.Id);
}
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
return View(model);
}
}
My Web.config :
<connectionStrings>
<add name="MyEFDataModelContainer" connectionString="metadata=res://*/MyEFDataModel.csdl|res://*/MyEFDataModel.ssdl|res://*/MyEFDataModel.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=DB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
But now, I have the error The specified type member ' UserName ' is not supported in LINQ to Entities . Only initializers , entity members , and entity navigation properties are supported . at the following line:
var result = await UserManager.CreateAsync(user, model.Password);
So, my first question would be, why do you need two contexts? Is there a reason that the aspnet tables have to be separate from the business tables?
Could you do something like:
public partial class MyEFDataModelContainer : IdentityDbContext<User>
{
public MyEFDataModelContainer ()
: base("name=MyEFDataModelContainer")
{
}
public DbSet<User> Users { get; set; }
}
Make sure to have your custom User class inherit from IdentityUser.
public partial class User : IdentityUser
{
public User()
{
this.Logs= new HashSet<Log>();
}
public int IdUser { get; set; }
public string AspNetUsersId { get; set; } //References to AspNetUsers.Id
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job{ get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
Then, change your AccountController to this connection string. This way everything is in the same database.
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<User>(
new UserStore<User>(new MyEFDataModelContainer())))
{
}
}
Although, to answer your question, yes you can wrap access to two contexts inside a transaction. You will have to deal with MSDTC, however, which, in my experience, can sometimes be frustrating.
I need your help in guiding may into good way of implement the Model in MVC4.
I will let you see my model. But I really don’t know how to link that to Membership Provider in MVC4
I want to build tender application system and I have the following models
Tender: who add projects?
Supplier/provider : who bid for projects
Projects: Projects added by tenders ( Done)
Requirements: each projects had several requirements.(Done)
I did the project And requirement Model.. But am not sure how to do the tender and suppliers!! Because both of them have to register ..!?
2.Is my relation many to many is correct? Between Project and Requirement table.?
Now those are my model with context:
public class ProjectContext : DbContext
{
public ProjectContext()
: base("ProjectsDB")
{
}
public DbSet<ProjectEntry> Entries { get; set; }
public DbSet<Requiernments> RequiernmentEntries { get; set; }
//public DbSet<UserProfile> UserProfiles { get; set; }
}
public class ProjectEntry
{
[Key]
public int ID { get; set; }
[Required]
public string ProjectName { get; set; }
public string Description { get; set; }
public string Statue {get; set; }
public string UplodedFiles { get; set; }
public string Budget { get; set; }
public string EstimateTime { get; set; }
public string Criterias { get; set; }
public DateTime? DueDate { get; set; }
}
public class Requiernments
{
[Key]
public int RequiernmentId { get; set; }
public int ID { get; set; }
public string RequiernmentName { get; set; }
/// <summary>
/// 1: Must to Have
/// 2: Nice to Have
/// 3: Should have
/// </summary>
public string RequiernmentType { get; set; }
public string RequiernmentPrioritet { get; set; }
public float RequiernmenWhight { get; set; }
public string ProviderAnswer { get; set; }
public string ProviderComments{ get; set; }
}:
update 2:
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
if (!Roles.RoleExists("Admin"))
Roles.CreateRole("Admin");
if (!Roles.RoleExists("Member"))
Roles.CreateRole("Member");
if (!Roles.RoleExists("Tender"))
Roles.CreateRole("Tender");
if (!Roles.RoleExists("Provider"))
Roles.CreateRole("Provider");
WebSecurity.CreateUserAndAccount(model.UserName, model.Password,
new
{
EmailAddress = model.EmailAddress
}, false);
Roles.AddUserToRole(model.UserName, "Member");
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
AND
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=user-Pc\SQL2012;Initial Catalog=MemberDB;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\MemberDB.mdf" providerName="System.Data.SqlClient" />
</connectionStrings>
image:
http://i58.tinypic.com/2rp8i86.png
If I understand correctly, in your application you have two roles, tender and supplier. In addition, you want to tender to be able to add project and then associate requirements with project.
In order to achieve that, first of all you need to config SimpleMembershipProvider to have two roles "tender" and "supplier"
First in the configuration file, replace the classic membership provider with SimpleMembershipProvider by doing this
enable migrations
seed membership and roles
protected override void Seed(MovieDb context)
{
//context.Movies.AddOrUpdate(...);
// ...
SeedMembership();
}
private void SeedMembership()
{
WebSecurity.InitializeDatabaseConnection("DefaultConnection",
"UserProfile", "UserId", "UserName", autoCreateTables: true);
var roles = (SimpleRoleProvider) Roles.Provider;
var membership = (SimpleMembershipProvider) Membership.Provider;
if (!roles.RoleExists("Admin"))
{
roles.CreateRole("Admin");
}
if (membership.GetUser("sallen",false) == null)
{
membership.CreateUserAndAccount("sallen", "imalittleteapot");
}
if (!roles.GetRolesForUser("sallen").Contains("Admin"))
{
roles.AddUsersToRoles(new[] {"sallen"}, new[] {"admin"});
}
}
step 1,2,3 reference:
Scott Allen's blog
Now create your model
public class Tender
{
public int TenderId { get;set;}
public int UserId {get;set;} //this links to the userid in the UserProfiles table
public virtual ICollection<Project> Projects {get;set;}
}
public class Project
{
public int ProjectId {get;set;}
public int TenderId {get;set;}
public virtual Tender Tender {get;set;}
public virtual ICollection<Supplier> Suppliers {get;set;}
public virtual ICollection<Requirement> Requirements {get;set;}
}
public class Supplier
{
public int SupplierId {get;set;}
public virtual ICollection<Project> Projects {get;set;}
}
public class Requirement
{
public int RequirmentId {get;set;}
public int ProjectId {get;set;}
public virtual Project Project {get;set;}
}
because supplier can bid multiple projects and projects can have multiple bidders, therefore supplier and project have a multiple to multiple relationship, you probably want to have a mapping table.
In the OnModelCreating Method,
modelBuilder.Entity<Project>()
.HasMany(p => p.Suppliers)
.WithMany(s => s.Projects)
.Map(map =>
{
map.ToTable("Project_Supplier_Map")
.MapLeftKey("SupplierId")
.MapRightKey("ProjectId");
});
Now you have your model and just need to decorate your class with Authorize attribute