How to retrieve the objects participating in a many-to-many relationship? - asp.net-core

I have a many-to-many relationship between User(Contributor) and TaskType. I want to assign to a variable only TaskTypes that have the current user among their contributors. Obviously, I can somehow do this using the functionality of the Entity Framework. But how? I use asp.net core 3.
Below I try unsuccessfully to do it:
public IQueryable<TaskType> ContributedTaskTypes
{
get
{
// This code doesn't work!
return _dbContext.TaskTypes.Where(t => t.Contributors.Contains(c => c.UserId == CurrentUserId));
}
}
Below are definitions of all models involved in this relationship:
public class TaskType
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public virtual List<Contribution> Contributors { get; set; }
}
public class Contribution
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int TaskTypeId { get; set; }
public TaskType TaskType { get; set; }
}
public class ApplicationUser : IdentityUser
{
public virtual List<Contribution> ContributedToTaskTypes { get; set; }
}

For those queries it is always easiest to do queries where you can dot to the result.
Here is the query with sql-like syntax
from row in _dbContext.Contribution
where row.UserId == CurrentUserId
select row.TaskType
By selecting row.TaskType instead of just row you get it correct entity.

Is that Contributors property retrieved correctly from DB? if it is not you must call Include() method to load/refer relational referenced entities
_dbContext.TaskTypes.Include(p=>p.Contributors).Where(..
more: https://learn.microsoft.com/en-us/ef/core/querying/related-data
In Addition, if EF Core Table Relation is not correctly defined, you should follow
this instruction: https://www.entityframeworktutorial.net/efcore/configure-many-to-many-relationship-in-ef-core.aspx

Related

How to set up relationship using entity framework core

I have Two Model
public class User: Entity
{
public string Gender { get; set; }
public string Name { get; set; }
}
And
public class CognitoUser : Entity
{
public string UserId { get; set; }
public User User{ get; set; }
public string CognitoName { get; set; }
}
I want to set Cognito.UserId as User.Id . I have written the following which is not working can you please correct me as i dont want to create a model CognitoUser into user model.
modelBuilder.Entity<CognitoUser>(e =>
{
e.ToTable("CognitoUser");
e.HasKey(p => p.UserId);
e.HasOne(x => x.User)
.HasForeignKey<User>(c => c.Id);
});
Primary keys are required in each Entity which is missing in your User Entity.
Using Fluent API is optional. If you set your classes right, Entity Framework will understand what you want to achieve.
Hints:
Use [Key] attribute to specify a property as primary key
Prefered primary key format would be {ClassName}{Id}
Use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] to force database
to automatically generate primary key for you.
You can use Guid as primary key type, it is always unique and hassle-free
Additionally, check out the code below to see how to create a relation.
public class User: Entity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UserId { get; set; }
public string Gender { get; set; }
public string Name { get; set; }
}
public class CognitoUser: Entity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid CognitoUserId { get; set; }
public string CognitoName { get; set; }
// relation
public Guid UserId { get; set; }
public User User { get; set; }
}
Visualization:

Extend Asp Net Core Identity Relation

In Asp net core (3.1) Identity i want add a many to many relationship between user and TourOperators.
(The concept is that many user can follow many tour operators).
I have the tour operators class:
public class TourOperator
{
public int Id { get; set; }
public ICollection<Follow> Follows { get; set; }
}
I have extended the UserIdentity class:
public class ApplicationUser: IdentityUser
{
public ICollection<Follow> Follow { get; set; }
}
I have the Follow class:
public class Follow
{
public long Id { get; set; }
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int TourOperatorId { get; set; }
public TourOperator TourOperator{ get; set; }
}
After execute the migration, why in the Follow table i have 4 field instead of 3?
I have the following field:
I think that ApplicationUserId couldn't be present
Entity Framework has no way to link the UserId and ApplicationUser properties. So you either need to follow convention, whereby EF can make an educated guess. The simplest option is to rename your string property:
public string ApplicationUserId { get; set; }
Alternatively, you can configure it, for example using an attribute:
[ForeignKey("ApplicationUser"]
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
Or in the OnModelCreating method, for example:
modelBuilder.Entity<Follow>()
.WithOne(f => f.ApplicationUser)
.HasForeignKey("UserId");

How to Extend Roles for Multiple Locations / Sites with Asp.Net Core Identity

I have an API that is used in a multi-site/multi-location environment. At the moment, each user has roles defined but is locked to only one location. I am needing to extend this out to where a user may have admin roles for one location and then may be standard user at another location. They may also have no roles/no access to a bunch of locations.
Here is what I am working with right now (asp.net core 2.2):
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LocationId { get; set; }
public virtual Locations Locations { get; set; }
public int? ContactPersonId { get; set; }
public virtual ContactPerson ContactPerson { get; set; }
}
public class Locations
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
public class ContactPerson
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
public virtual ICollection<ContactOrganizationPerson> ContactOrganizationPeople { get; set; }
public virtual ICollection<ContactAddress> ContactAddresses { get; set; }
public virtual ICollection<ContactPhone> ContactPhones { get; set; }
public virtual ICollection<ContactEmail> ContactEmails { get; set; }
}
I am planning on changing the ApplicationUser to Locations table relationship to a Many to Many which would link the User to the Locations they are allowed to access. I have though about placing a payload in the M2M relationship table that would specify UserId, LocationId and Roles, but I would rather let Identity handle it if possible.
Is there a way to extend AspNetUserRoles so that I can specify a User to Role relationship for each location? Or is there a better way to accomplish this?
I'm not sure if this is going to help you, but I have extended .Net Core with functionality with IAuthorizationRequirement.
public class CustomRequirement : IAuthorizationRequirement
{
public CustomRequirement ()
{
}
}
Create a new class
public class CustomHandler : AuthorizationHandler<CustomRequirement>
Override HandleRequirementAsync
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
Here you can extract info about user from DB, compare, etc. If user is not allowed, return
return Task.CompletedTask;
If user is allowed, then use
context.Succeed(requirement);
Before returning. Then in your startup.cs:
services
.AddAuthorization(options =>
{
options.AddPolicy("CustomPolicy", policy =>
policy.Requirements.Add(new CustomRequirement()));
})
And then in your controllers you can add attribute
[Authorize(Policy = "CustomPolicy", Roles = "Admin")]
If requirement is not meet, user will get 401 unauthorized, which might not be what you want.

Entity framework code first, delete childs by updating parent

As entity framework states, "Code first", here we go with the code first...
public class BaseModel
{
[Key]
public Guid Id { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateChanged { get; set; }
public BaseModel()
{
this.Id = Guid.NewGuid();
this.DateCreated = DateTime.Now;
this.DateChanged = DateTime.Now;
}
}
public class Association: BaseModel
{
public string Name { get; set; }
public string Type { get; set; }
public virtual List<Rule> Rules { get; set; }
public Association()
: base()
{
}
}
public class Rule: BaseModel
{
[ForeignKey("Association")]
public Guid AssociationId { get; set; }
//[Required]
public virtual Association Association { get; set; }
//[Required]
public string Name { get; set; }
public string Expression { get; set; }
public virtual List<Action> Actions { get; set; }
public Rule()
: base()
{
}
}
public class Action: BaseModel
{
public string Name { get; set; }
public string ActionType { get; set; }
[ForeignKey("Rule")]
public Guid RuleId { get; set; }
public virtual Rule Rule { get; set; }
public int Order { get; set; }
public Action()
: base()
{
}
}
So these are my four model classes that are using entity framework code first.
Each inherit from the baseclass, so they all have an Id Guid as Primary Key.
An Association has a list of rules. (Rule has FK to Association)
A Rule as has a list of actions. (Action has FK to Rule)
What I would like to do is only change and save the most upwards class = Association.
For example when deleting a rule, I would like this code to work:
public ActionResult DeleteRule(Guid assId, Guid ruleId)
{
Association ass = this.DataContext.Associations.FirstOrDefault(a => a.Id == assId);
ass.Rules.RemoveAll(r => r.Id == ruleId);
this.DataContext.SaveChanges();
return RedirectToAction("Index");
}
On the context.savechanges this is giving me this error:
'The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.'
This error also occurs when deleting an action.
Is there a way to change the most upper (Association) object AND ONLY changing things to this Association.
I DO NOT want to say context.Rules.remove(...) or context.actions.remove(...)
here's the source: http://server.thomasgielissen.be/files/mvctesting.zip
you need VS2012, all nuget packages are included in zip and you should be able to build and run the project.
Thanks in advance for your feedback!
Greetz,
Thomas
I you want to fix this issue, you should store your relations through junction tables. I don't think that you can achieve what you need, with this model.
However if you put a junction table(or entity) between your entities, you can easily remove child objects and update parent object.
For example, put a junction entity between Association and Rule:
public class AssociationRule: BaseModel
{
public Guid AssociationId { get; set; }
public Guid RuleId { get; set; }
[ForeignKey("AssociationId")]
public virtual Association Association { get; set; }
[ForeignKey("RuleId")]
public virtual Rule Rule { get; set; }
public Association()
: base()
{
}
}
Now, you can easily remove any rule from any association:
public ActionResult DeleteRule(Guid assId, Guid ruleId)
{
AssociationRule assr = this.DataContext
.AssociationRuless
.FirstOrDefault(ar => ar.AssociationId == assId && ar.RuleId == ruleId);
this.DataContext.AssociationRules.Remove(assr);
this.DataContext.SaveChanges();
return RedirectToAction("Index");
}

One to one relationship - code first

I'm trying to make one to one relationship. I don't want to use fluent API if it is not necessary. This is what I tried so far:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Person
{
public int Id { get; set; }
[ForeignKey("UserId")]
public UserProfile UserProfile { get; set; }
}
Yes, I know there are few similar questions outhere, but none of them were short and clear. Lot of them also did not work.
It depends a little on what type of table structure you want to achieve. There are various ways of doing this, and there is a good walkthrough for all the options, from Shared Primary Key Assocations to One-to-One Foreign Key Associations in those links. Unfortunately those links make more use of Fluent than Annotations. The samples below use Annotations, as you need.
Shared Primary Key
In theory the Shared Primary Key (horizontal table partitioning, in database terms) is the "correct way". It is also the smallest change you need to do to be able to generate a migration (which will use a Shared Primary Key Association). Note that I would change Person.Id to Person.UserId to better show your intent:
// tested in EF 5 and MVC 4.5.
[Table("UserProfile")]
public class UserProfile {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
[Table("Person")] // not required, added for clarity in sample code
public class Person {
// Note the change of property name to reflect that this is a shared primary key,
// using the UserId column in UserProfile as the Primary Key
[Key]
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual UserProfile UserProfile { get; set; }
}
// The generated migration:
public partial class AddTable_Person : DbMigration
{
public override void Up() {
CreateTable(
"dbo.Person",
c => new {
UserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.UserId)
.ForeignKey("dbo.UserProfile", t => t.UserId)
.Index(t => t.UserId);
}
public override void Down(){
DropIndex("dbo.Person", new[] { "UserId" });
DropForeignKey("dbo.Person", "UserId", "dbo.UserProfile");
DropTable("dbo.Person");
}
}
this then gives you, in effect a 1:0-1 relationship between UserProfile (which is mandatory) and People (which is optional, but can have one per person at the most.
If you want to use Id in Person then do the following (the migration will change accordingly):
public class Person {
public int Id { get; set; }
[ForeignKey("Id")]
public UserProfile UserProfile { get; set; }
}
Shared Primary Key with two-way navigation
If you want to navigate from UserProfile to Person you have more work to do. Simply adding public virtual Person Person { get; set; } to UserProfile will give you an error:
Unable to determine the principal end of an association between the types 'Test.Models.UserProfile' and 'Test.Models.Person'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
So, we fix it with [Required] on the Person.UserProfile property (Person requires UserProfile). This gives the same migration as before.
// tested in EF 5 and MVC 4.5.
[Table("UserProfile")]
public class UserProfile {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
[ForeignKey("UserId")]
public virtual Person Person { get; set; }
}
[Table("Person")] // not required, added for clarity in sample code
public class Person {
[Key]
public int UserId { get; set; }
[ForeignKey("UserId")]
[Required]
public virtual UserProfile UserProfile { get; set; }
}
Again, this works if you use Id for Person instead of UserId:
public class Person {
[Key]
public int Id { get; set; }
[ForeignKey("Id")]
[Required]
public virtual UserProfile UserProfile { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual Person Person {get;set;}
}
public class Person
{
public int Id { get; set; }
public int UserProfileUserId { get; set; } //Foreign Key
public virtual UserProfile UserProfile { get; set; }
}
If you want to create a one-to-one relationship the first thing you must clarify is what is the principal and what the dependent entity in this relationship. Can a Person exist without UserProfile or can a UserProfile exist without Person?
Because you have started to apply a [ForeignKey] attribute in Person I am assuming now that Person is the dependent, i.e. it cannot exist without UserProfile.
Correct application of the [ForeignKey] attribute would then be:
public class Person
{
[ForeignKey("UserProfile")]
public int Id { get; set; }
public UserProfile UserProfile { get; set; }
}
I am not sure if that is sufficient because you don't have a Person navigation property in UserProfile. If it doesn't work add this property to UserProfile:
public Person Person { get; set; }
If you don't want to have such a navigation property you can't avoid Fluent API:
modelBuilder.Entity<Person>()
.HasRequired(p => p.UserProfile)
.WithOptional();