I would like to set up a many to many relationship in ASP.NET MVC4.
The goal is to extend the default UserProfile class with a list of Timeline objects, which belong to the user.
A Timeline could be shared by multiple users, so the Timeline class should have an list of UserProfile objects aswell.
Timeline class:
namespace MvcApplication1.Models
{
public class Timeline
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public List<UserProfile> Users { get; set; }
}
public class TimelineContext : DbContext
{
public DbSet<Timeline> Timelines { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
// Added the following because I saw it on:
// http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Timeline>()
.HasMany(c => c.Users)
.WithMany(s => s.Timelines)
.Map(mc =>
{
mc.ToTable("TimelineOwners");
mc.MapLeftKey("TimelineId");
mc.MapRightKey("UserId");
});
}
}
}
UserProfile class (default class with an added property):
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Timeline> Timelines { get; set; }
// Added the following because I saw it on:
// http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserProfile>()
.HasMany(c => c.Timelines)
.WithMany(s => s.Users)
.Map (mc =>
{
mc.ToTable("TimelineOwners");
mc.MapLeftKey("UserId");
mc.MapRightKey("TimelineId");
});
}
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public List<Timeline> Timelines { get; set; }
}
I have a connection table with foreign keys:
When creating an instance of Timeline, the Users list is null:
Timeline timeline = db.Timelines.Find(id); // timeline.Users = null
Can somebody please enlighten me, how should I set this up working?
I'm totally new to ASP.NET MVC4.
Edit 1: I understand I should not extend UserProfile but create another class to store users. Once the many-to-many relationship works, I will refactor and go into that direction.
But first I would like to know why is it not working.
Edit 2:
The double context also caused problems, two databases were created for the two contexts and the pure join table was empty in one of them.
I suggest that you work through this article about the options how you can load navigation properties with Entity Framework. This is very basic knowledge which is important for every kind of relationship, not only many-to-many relationships.
Looking at that article you will find then that this line...
Timeline timeline = db.Timelines.Find(id);
...does not load any related entities. So, it's expected that timeline.Users is null, even if the entities are related in the database.
If you want to load the Users you can use eager loading:
Timeline timeline = db.Timelines.Include(t => t.Users)
.SingleOrDefault(t => t.Id == id);
This is a single database query. Or to enable lazy loading you have to mark your navigation properties as virtual:
public virtual List<UserProfile> Users { get; set; }
//...
public virtual List<Timeline> Timelines { get; set; }
You can then use your original code:
Timeline timeline = db.Timelines.Find(id); // first query
var users = timeline.Users; // second query
This will run two separate queries. The second is performed as soon as you access the navigation property for the first time.
BTW: Is there a reason why you have two context classes - TimelineContext and UsersContext? "Normally" you need only one context.
I'm not a fan of messing with the working of the internal userprofile. I would suggest creating your own user class, linking it to the simplemembershipprovider and adding functionality there. At max you'll extend the accountclasses a little to add more fields to register with, but that's about it.
Follow this extremely handy guide to get things working and let me know if you encounter an error.
Related
I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});
I have built a new Web Application that uses the template Visual Studio provides and included MVC and Web API. The default authorization mechanism is Identity and the database interaction is done using Entity Framework with Code-first method of creating the database.
I have three requirements:
A user can have a list of Children objects
I do not want to use a "relationship" object
All users already exist on the AspNetUsers table, because they all need to be able to login, so I do not want another table to maintain user data
In theory, multiple parents could have reference to multiple children, but for this example, we will just consider it a one-to-many relationship.
In my application, I need to have an ApplicationUser have a list of ChildUsers as a collection of ApplicationUser such as shown below.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string ShirtSize { get; set; }
public ICollection<ApplicationUser> Children { get; set; }
}
I want these users to be accessible as shown above (a collection of ApplicationUser), not a collection of Relationship object that ties them together such as:
public class Relationship
{
public String ParentId { get;set; }
public String ChildId { get;set; }
}
Can a new table be created and exist on the database without having a code-first model for it to know how to create a relationship table?
What are available solutions to this problem?
After some research, and experimentation, I have found bits and pieces of guidance to arrive at a solution that works.
In order for an intermediate table to be created to maintain the relationship, the ApplicationDbContext OnModelCreating function needs to know what it should look like. I have told it to create a new table that is not bound to an object by using the modelBuilder shown in the code below. Unfortunately, I do not have the links to the articles that guided me to this.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base( "DefaultConnection", throwIfV1Schema: false )
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
modelBuilder.Entity<ApplicationUser>()
.HasMany( p => p.ChildUsers )
.WithMany()
.Map( m =>
{
m.MapLeftKey( "Father_Id" );
m.MapRightKey( "Son_Id" );
m.ToTable( "father_son_relation" );
} );
}
}
Additionally, when you need to add Children to the parent ApplicationUser, you will need to do some tweaking as you are about to insert so that it updates the database correctly. I definitely want the UserManager to do the creation of the user for me, but that means that when I go to add the user to my list of Children with the code below, it tries to add it again and throws an exception because it already exists.
var result = await UserManager.CreateAsync( user, model.Password );
var myUserId = User.Identity.GetUserId();
var users = AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.ChildUsers );
var u2 = users.First();
u2.ChildUsers.Add( user );
await AppDbContext.SaveChangesAsync();
After finding this question, I researched the EntityStates and found that adding the following line before calling SaveChanges resolved the exception and it no longer attempts to add it again.
AppDbContext.Entry( user ).State = EntityState.Unchanged;
TADA!!! Now to select them from the database using EF, you can then use the following code:
AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.Children ).First();
Since I am only getting one level of Children this will work ok, after that you risk circular references.
Comments and ideas to improve the code are welcome.
using the latest (current) RC1 of asp.net5 I'm looking at creating a simple relationship between a User entity and a WorkLog entity.
Is it possible to use the ApplicationUser Class from Identity as a starting point and use the ApplicationUser key which is defined as the linking key? I have had problems extending the ApplicationUser in the past and therefore generated a seperate dbcontext (pointing to the same database) and created my own plumbing in order to pass the IdentityUsers Id into my seperate dbcontext. Does anyone have any examples of extending the IdentityDbContext adding foreign key tables mapping to the IdentityUser Class?
Example below
//DBContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<WorkLogItem> WorkLogItems { 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<WorkLogItem>(
e =>
{
e.Property(p => p.id).IsRequired().UseSqlServerIdentityColumn();
});
}
}
//WorkLogItem
public class WorkLogItem
{
public int id { get; set;}
public String UserId { get; set; }
public int Hours { get; set; }
public String Description { get; set; }
}
//ApplicationUser
public class ApplicationUser : IdentityUser
{
public ICollection<WorkLogItem> WorkLogItems { get; set; }
}
Doing what you've asked is expected to work out of the box. You can look at this commit to see the difference between a newly created MVC 6 project with Identity and your schema above.
Registering a user, and refreshing /Home/Index causes WorkLogItems to be added as expected. Note you don't need a separate DB context for this.
public IActionResult Index()
{
var user = _db.Users.Include(p => p.WorkLogItems).FirstOrDefault();
if (user != null)
{
user.WorkLogItems.Add(new WorkLogItem { Description = "New item added" });
_db.SaveChanges();
ViewBag.WorkItems = user.WorkLogItems.ToList();
}
else ViewBag.WorkItems = new WorkLogItem[] { };
return View();
}
The key items to be aware of when you add any collection to an existing entity are;
Make sure you add the migration and update the databse
Make sure you use Include on the query because EF7 does not support Lazy Loading.
I have a Generic Repository class using code first to perform data operations.
public class GenericRepository<T> where T : class
{
public DbContext _context = new DbContext("name=con");
private DbSet<T> _dbset;
public DbSet<T> Dbset
{
set { _dbset = value; }
get
{
_dbset = _context.Set<T>();
return _dbset;
}
}
public IQueryable<T> GetAll()
{
return Dbset;
}
}
I have an entity class Teacher, which maps to an existing table "Teacher" in my database, with exactly the same fields.
public class Teacher
{
public Teacher()
{
//
// TODO: Add constructor logic here
//
}
public int TeacherID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
I have the following code below which binds data from Teacher to a repeater control.
GenericRepository<Teacher> studentrepository = new GenericRepository<Teacher>();
rptSchoolData.DataSource = studentrepository.GetAll().ToList();
rptSchoolData.DataBind();
But I get an exception exception "The entity type Teacher is not part of the model in the current context". Do I have to do any additional work when using an existing database for code first?
You must create a context class that derives from DbContext. The class should have properties of type DbSet<T> which will give EF enough information to create and communicate with a database with default naming and association conventions. It will use properties like Student.Teacher (if any) to infer foreign key associations:
public class MyContext: DbContext
{
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Student> Students { get; set; }
...
}
If the defaults are not what you want, or when you've got an existing database that you want to match with the names and associations in your model you can do two (or three) things:
Override OnModelCreating to configure the mappings manually. Like when the tables in the database have those ugly prefixes (to remind people that they see a table when they see a table):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Teacher>()
.Map(e => e.ToTable("tblTeacher"));
...
}
(Less favorable) Use data annotations to do the same.
Turn it around and use Entity Framework Powertools to reverse-engineer a database into a class model including fluent mappings and a DbContext-derived context. Maybe easier to modify an existing model than to start from scratch.
I've just installed the new Entity Framework 4.1 NuGet package, thus replacing the EFCodeFirst package as per NuGet intructions and this article of Scott Hanselman.
Now, imagine the following model:
public class User
{
[Key]
public string UserName { get; set; }
// whatever
}
public class UserThing
{
public int ID { get; set; }
public virtual User User { get; set; }
// whatever
}
The last EFCodeFirst release generated a foreign key in the UserThing table called UserUserName.
After installing the new release and running I get the following error:
Invalid column name 'User_UserName'
Which of course means that the new release has a different FK naming strategy. This is consistent among all other tables and columns: whatever FK EFCodeFirst named AnyOldForeignKeyID EF 4.1 wants to call AnyOldForeignKey_ID (note the underscore).
I don't mind naming the FK's with an underscore, but in this case it means having to either unnecessarily throw away the database and recreate it or unnecessarily renaming al FK's.
Does any one know why the FK naming convention has changed and whether it can be configured without using the Fluent API?
Unfortunately, one of the things that didn't make it to this release is the ability to add custom conventions in Code First:
http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-release-candidate-available.aspx
If you don't want to use the fluent API to configure the column name (which I don't blame you), then most straight forward way to do it is probably using sp_rename.
Why don't you do the following?
public class User
{
[Key]
public string UserName { get; set; }
// whatever
}
public class UserThing
{
public int ID { get; set; }
public string UserUserName { get; set; }
[ForeignKey("UserUserName")]
public virtual User User { get; set; }
// whatever
}
Or, if you don't want to add the UserUserName property to UserThing, then use the fluent API, like so:
// class User same as in question
// class UserThing same as in question
public class MyContext : DbContext
{
public MyContext()
: base("MyCeDb") { }
public DbSet<User> Users { get; set; }
public DbSet<UserThing> UserThings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserThing>()
.HasOptional(ut => ut.User) // See if HasRequired fits your model better
.WithMany().Map(u => u.MapKey("UserUserName"));
}
}