How to create a bridging table between DbContext and IdentityDbContext? - entity-framework

I want to create a bridging table between dbo.AspNetUsers and dbo.Recipe called dbo.Binder. A user can save many recipes and a recipe can be saved by many users (many-to-many).
I'm able to create bridging tables within my DbContext fine, called RecipeContext, e.g dbo.Recipe, dbo.TagRecipe and dbo.Tag, but I am confused how I'm suppose to do this between two different DbContexts, that being RecipeContext (DbContext) and ApplicationDbContext(IdentityDbContext<IdentityUser>).
One solution I read was for RecipeContext to inherit from IdentityDbContext<IdentityUser>, like so:
public class RecipeContext :IdentityDbContext<IdentityUser>
Though when I do this and run
Update-Database -Context RecipeContext
I get an error
There is already an object named `AspNetRoles` in the database
I suppose this is from when I had the contexts separate, and yes, the AspNetRoles/identity tables are already created in my database from before.
I'm wondering if for one, I'm on the right track and two, what do I do about the fact the Identity tables already exist?
Thank you
For reference
RecipeContext:
public class RecipeContext :IdentityDbContext<IdentityUser> /*: DbContext*/
{
public RecipeContext(DbContextOptions options) : base(options) { }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TagRecipe> TagRecipes { get; set; }
public DbSet<StarRating> StarRatings { get; set; }
public DbSet<Binder> Binders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Identity
base.OnModelCreating(modelBuilder);
// 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);
...(code left out)
// Override pural table names to singular
modelBuilder.Entity<Binder>().ToTable("Binder");
// Declare use UserId and RecipeId as primary keys for Binder
modelBuilder.Entity<Binder>()
.HasKey(b => new { b.UserId, b.RecipeId });
modelBuilder.Entity<Binder>()
.HasOne<ApplicationUser>(b => b.User)
.WithMany(u => u.Binders)
.HasForeignKey(b => b.UserId);
modelBuilder.Entity<Binder>()
.HasOne<Recipe>(b => b.Recipe)
.WithMany(u => u.Binders)
.HasForeignKey(b => b.RecipeId);
}
}
ApplicationDbContext (this is to be merged into RecipeContext):
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
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);
}
}
Binder class:
public class Binder
{
public string UserId { get; set; }
public int RecipeId { get; set; }
public virtual ApplicationUser User { get; set; }
public Recipe Recipe { get; set; }
}
ApplicationUser class (this was self created to create a collection of Binders)
public class ApplicationUser : IdentityUser
{
public ICollection<Binder> Binders { get; set; }
}
Recipe class:
public class Recipe
{
public int Id { get; set; }
...(code left out)
public ICollection<Binder> Binders { get; set; }
}
Startup class - ConfigureServices method (I feel some adjustments will need to be made if the two dbContexts are merging)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Identity
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddRazorPages(); // Added - Identity scaffhold Views
// Recipe context
services.AddDbContext<RecipeContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
}
appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=WCIMDBCoreEF1ModelTest;Trusted_Connection=True;MultipleActiveResultSets=true",
"ApplicationDbContextConnection": "Server=(localdb)\\mssqllocaldb;Database=MVCApp;Trusted_Connection=True;MultipleActiveResultSets=true"
}

Related

Entity Framework Core 2.2 Owned Entity with UseLazyLoadingProxies

I am currently working on a codebase, to which I want to add a number of new entities with corresponding owned entities. Because, in some other part of the codebase I won't touch, UseLazyLoadingProxies is called; I receive the following exception:
System.InvalidOperationException : Navigation property 'Foo' on entity type 'FooOwner' is not virtual. UseLazyLoadingProxies requires all entity types to be public, unsealed, have virtual navigation properties, and have a public or protected constructor.
If I mark the property as virtual, the owned entity goes into a new table; which I do not want either.
According to github issues I encountered, these seem to be the expected behavior.
My question is this: Is there a way to work around this problem, such that, I can somehow mark the owned entity to be stored in the same table as the owner entity, and if possible to be always Included, eagerly loaded.
using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using NUnit.Framework;
namespace StackOverflowObjectContext.Tests
{
public class Foo
{
public long Id { get; set; }
public int Data { get; set; }
}
public class FooOwner
{
public int Id { get; set; }
public Foo Foo { get; set; }
}
public class FooOwnerMap : IEntityTypeConfiguration<FooOwner>
{
public void Configure(EntityTypeBuilder<FooOwner> builder)
{
builder.HasKey(x => x.Id);
builder.HasOne(x => x.Foo);
}
}
public class StackOverflowObjectContext : DbContext
{
public StackOverflowObjectContext(DbContextOptions options) : base(options) { }
DbSet<FooOwner> FooOwners { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FooOwnerMap());
base.OnModelCreating(modelBuilder);
}
}
[TestFixture]
public class StackOverflowTest
{
StackOverflowObjectContext _objectContext;
[SetUp]
public void SetUp()
{
var builder = new DbContextOptionsBuilder<StackOverflowObjectContext>()
.UseSqlServer(#"Data Source=.\SQLEXPRESS;Initial Catalog=StackOverflow;Integrated Security=True")
.UseLazyLoadingProxies();
_objectContext = new StackOverflowObjectContext(builder.Options);
}
[Test]
public void CanGenerateCreateScript()
{
var script = _objectContext.Database.GenerateCreateScript();
Debug.WriteLine(script);
}
}
}
You should use OwnsOne instead of HasOne

Manually map 1 to Many Relationship

I am trying to map an existing database in EF with code first. The provider (jetEntityFrameworkProvider) does not support DB first.
I am trying to map the Table "Component" (1) to the Table "ComponentText" (Many)
This is what I have
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Component>().Map(m =>
{
m.Properties(p => new { p.ComponentText });
m.ToTable("ComponentText");
});
modelBuilder.Entity<ComponentText>().HasKey(t => t.ComponentCounter);
}
When I run it I get the follow error
System.InvalidOperationException: 'The property 'ComponentText' on type 'Component' cannot be mapped because it has been explicitly excluded from the model or it is of a type not supported by the DbModelBuilderVersion being used.'
These are my models with only the relevant properties
Component
[Table("Component")]
public class Component
{
[Key]
[Column("Counter")]
public int Id { get; set; }
[Column("Name")]
public virtual ICollection<ComponentText> ComponentText { get; set; }
}
ComponentText
[Table("ComponentText")]
public class ComponentText
{
[Key]
[Column("Counter")]
public int Id { get; set; }
public int TextId { get; set; }
public string Text { get; set; }
//** Foreign Key
public int ComponentCounter { get; set; }
}
ETA:
I've changed my code per Backs answer. However, it is still not working. I have tried several variations. .HasRequired(), .HasOptional().
Note I removed m.ToTable("ComponentText"); As Component is already mapped in the class to the "Component" Table.
I am getting 0 results and receiving this error in Results View
Error = The function evaluation requires all threads to run.
If I uncomment the section line in the comment modelBuilder.Entity<ComponentText>().HasKey(t => t.ComponentCounter);
I get this error
Component_ComponentText_Target: : Multiplicity is not valid in Role 'Component_ComponentText_Target' in relationship 'Component_ComponentText'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
public class ProjectContext : DbContext
{
private DbConnection con = new JetConnection();
public ProjectContext() : base(new JetConnection(#"Provider=Microsoft.Jet.OLEDB.4.0; Data Source = 'C:\Users\Ben-Laptop\Desktop\Test-Project.sep'; User Id = Admin; Jet OLEDB:Database Password = SEEME;"), true)
{
Database.SetInitializer<ProjectContext>(null);
}
public DbSet<Component> Components { get; set; }
public DbSet<Content> Contents { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Component>().HasMany(p => p.ComponentText).WithOptional().HasForeignKey(p => p.ComponentCounter);
//modelBuilder.Entity<ComponentText>().HasKey(t => t.ComponentCounter);
}
}
Remove m.Properties(p => new { p.ComponentText }); because it only maps property ComponentText
Add mapping for collection
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Component>().Map(m =>
{
m.ToTable("ComponentText");
})
.HasMany(p => p.ComponentText)
.WithRequired()
.HasForeignKey(p => p.ComponentCounter);
}
Entity Framework Fluent API - Relationships

EF stores subset of records into another table

I have a header-child tables with the child having different types but stored in the same table (TPH).
On top of this, user can snapshot a copy of a header and its children records and I would like to store the snapped copy into a different table since these snapshot records would be less frequent to view/modify.
To achieve this, I am mixing TPC to my existing TPH.
The new structure is as follows:
public class Header
{
private IList<Child> _childs = new List<Child>();
private IList<ChildSnapshot> _childSnapshots = new List<ChildSnapshot>();
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IList<Child> Childs { get { return _childs; } }
public IList<ChildSnapshot> ChildSnapshots { get { return _childSnapshots; } }
}
public abstract class ChildBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Header")]
public int HeaderId { get; set; }
[ForeignKey("HeaderId")]
public virtual Header Header { get; set; }
}
public abstract class Child : ChildBase
{
}
public class Child1 : Child
{
}
public class Child2 : Child
{
}
public abstract class ChildSnapshot : ChildBase
{
}
public class ChildSnapshot1 : ChildSnapshot
{
}
public class ChildSnapshot2 : ChildSnapshot
{
}
And the database context:
public class TestContext : DbContext
{
public DbSet<Header> Headers { get; set; }
public DbSet<Child> Childs { get; set; }
public DbSet<ChildSnapshot> ChildSnapshots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Header>().Map(m => m.ToTable("Header"));
modelBuilder.Entity<Child>()
.Map<Child>(m =>
{
m.MapInheritedProperties();
m.ToTable("Child");
})
.Map<Child1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<Child2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
modelBuilder.Entity<ChildSnapshot>()
.Map<ChildSnapshot>(m =>
{
m.MapInheritedProperties();
m.ToTable("ChildSnapshot");
})
.Map<ChildSnapshot1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<ChildSnapshot2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
}
}
It works perfectly after many trials and errors. However, I've got to create 2 list properties in Header class. Is it possible to have only 1 list property of ChildBase type? I got the following error when I do so.
The type 'Child' cannot be mapped as defined because it maps inherited
properties from types that use entity splitting or another form of
inheritance. Either choose a different inheritance mapping strategy so
as to not map inherited properties, or change all types in the
hierarchy to map inherited properties and to not use splitting.
Why is the behaviour determined by the type of the container list? Can't EF infer from the type of the object in the list instead?
FYI I am using EF 4.3.

check if a property is ignored by EntityFramework

Using EntityFramework 4.3 w/POCOs.
how can I check if a property on a model is ignored or not.
In my DBContext Class Hierarchy I am ignoring a property by
modelBuilder.Entity<EClass>().Ignore (f => f.IgnoredProperty());
In my BaseContext class, I need to check if that property is ignored or not.
private void ProcessGlobalConvention(DbModelBuilder modelBuilder, IGlobalConvention convention)
{
modelBuilder.Entity<typeof(this.GetType())>("Ignored Property");
}
How can I do that?
Thanks
Use the EF power tools http://www.infoq.com/news/2013/10/ef-power-tools-beta4 to view your model. Is the property there?
Create a database. Is the column there?
Look at the Database.LogSqlEvents http://blog.oneunicorn.com/2013/05/08/ef6-sql-logging-part-1-simple-logging/ and parse the sql to see if the field name appears...
....unless you really want a code solution...?
IN WHICH CASE
New up your DbContext
Create one record and add it to the relevant DbSet
Get the DbEntityEntry
Look in CurrentValues.PropertyNames. Is your property there?
[TestMethod]
public void CreateDatabase()
{
Database.SetInitializer(new DropCreateDatabaseAlways<HomesContext>());
var db = new HomesContext();
Assert.IsFalse(db.Homes.Any());
var home = db.Homes.Create();
db.Homes.Add(home);
var entry = db.Entry(home);
Assert.IsTrue(entry.CurrentValues.PropertyNames.Contains("MaxResidents"));
Assert.IsTrue(entry.CurrentValues.PropertyNames.Contains("MaxStaff"));
Assert.IsFalse(entry.CurrentValues.PropertyNames.Contains("CurrentResidents"));
Assert.IsFalse(entry.CurrentValues.PropertyNames.Contains("CurrentStaff"));
}
public class HomesContext:DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Home>().Ignore(x => x.CurrentResidents);
base.OnModelCreating(modelBuilder);
}
public DbSet<Home> Homes { get; set; }
}
public class Home
{
public int HomeId { get; set; }
public string HomeName { get; set; }
public int MaxResidents { get; set; }
public int MaxStaff { get; set; }
public int CurrentResidents { get; set; }
[NotMapped]
public int CurrentStaff { get; set; }
}

Entity framework context and structure map disposing

I have strange problem with disposing entity framework connection in asp.net mvc application.
I have simple structure for example :
Entity :
public class EmployeeReport
{
public int EmployeeReportId { get; set; }
public DateTime Created { get; set; }
public Decimal Hours { get; set; }
public string Comment { get; set; }
public int EmployeeId { get; set; }
public int ContractId { get; set; }
public int ServiceId { get; set; }
public virtual ReportContract Contract { get; set; }
public virtual ReportService Service { get; set; }
public virtual Employee Employee { get; set; }
}
Entity mapper :
public class EmployeeReportMapper : EntityTypeConfiguration<EmployeeReport>
{
public EmployeeReportMapper()
{
ToTable("intranet_employee_reports");
HasKey(x => x.EmployeeReportId);
Property(x => x.Created).HasColumnName("Created").IsRequired();
Property(x => x.Comment).HasColumnName("Comment").IsOptional();
Property(x => x.Hours).HasColumnName("Hours").IsRequired();
HasRequired(x => x.Employee).WithMany().HasForeignKey(x => x.EmployeeId);
HasRequired(x => x.Service).WithMany().HasForeignKey(x => x.ServiceId);
HasRequired(x => x.Contract).WithMany().HasForeignKey(x => x.ServiceId);
}
}
DbContext - interface
public interface IDbContext : IDisposable
{
IDbSet<EmployeeReport> EmployeeReports { get; }
}
DbContext - implementation
public class IntranetDbContext : DbContext,IDbContext
{
public IDbSet<EmployeeReport> EmployeeReports { get; set; }
...
public IntranetDbContext() : base("IntranetDb")
{
Database.SetInitializer<IntranetDbContext>(null);
}
public void Commit()
{
SaveChanges();
}
public void ChangeEntityState(object entity, EntityState entityState)
{
Entry(entity).State = entityState;
}
public void ExecuteSql(string query, SqlParameterCollection parameterCollection)
{
Database.ExecuteSqlCommand(query, parameterCollection);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* Register custom mapping class */
modelBuilder.Configurations.Add(new EmployeeReportMapper());
....
base.OnModelCreating(modelBuilder);
}
}
Finally my structure map configuration :
public class CoreRegistry : Registry
{
public CoreRegistry()
{
For<IDbContext>().HttpContextScoped().Use<IntranetDbContext>();
...
}
}
and Global.asax :
protected void Application_EndRequest(object sender, EventArgs e)
{
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}
Ok, now the problem, in my application i using standard constructor dependency injection
or call ObjectFactory.GetInstance().
In one of my controller i call service class, which has access to dbcontext and fetch some entites.
Unfortunately i get classic exception :
The ObjectContext instance has been disposed and can no longer be used
for operations that require a connection.
This is strange, because service is called during request and all data are forced to client in controller...
Any idea, where I do mistake?
EDIT :
Service code :
public class EmployeeService : IEmployeeService
{
/// <summary>
/// IDbContext reference
/// </summary>
private readonly IDbContext _dbContext;
public EmployeeService(IDbContext dbContext)
{
_dbContext = dbContext;
}
public List<Employee> GetSubordinateEmployees(Employee employee)
{
List<Employee> employees = new List<Employee>();
foreach (var unit in employee.OrganizationUnits.ToList()) /* throw exception*/
{
foreach (var childrenUnit in unit.ChildrenUnits)
{
employees.AddRange(childrenUnit.Employees);
}
}
return employees.Distinct().ToList();
}
Controller :
private readonly IEmployeeService _employeeService;
public EmployeeReportController(IEmployeeService employeeService)
{
_employeeService = employeeService;
}
[HttpGet]
public ActionResult SearchReports()
{
List<Employee> employees = _employeeService.GetSubordinateEmployee(IntranetSession.Current.LoggedAccount.Employee).ToList(); // Exception!
...
return View();
}
}
Your code doesn't use current DbContext at all. The problem is:
IntranetSession.Current.LoggedAccount.Employee
Followed by:
employee.OrganizationUnits.ToList()
Your employee stored in session was loaded with context which is already disposed but it still keeps reference to that context. When you loaded that employee you didn't eager load his organizations so once you access her OrganizationUnits it will try to trigger lazy loading on disposed context.
There are two ways to avoid this problem:
Eager load all information you need to use from your employee stored in session. It means retrieving employee like context.Employees.Include(e => e.OrganizationUnits).Single(...)
Store only employee's Id in session and load on-demand data you need
If you want to cache whole employee in session make sure that you will disable proxy creation for objects stored in session by calling :
context.Configuration.ProxyCreationEnabled = false;
It will ensure that cached data will not keep reference to disposed context (which btw. prevent garbage collector to collect context and all its referenced objects).