0

I am new to ASP.NET MVC, and I'm creating an app for attending music events. There is a button for user to follow an artist to get all upcoming events.

When clicking on that button I get an error:

Entities in 'ApplicationDbContext.Followings' participate in the 'ApplicationUser_Followees' relationship. 0 related 'ApplicationUser_Followees_Source' were found. 1

This is the code of the model:

public class Following
{
    public ApplicationUser Followers { get; set; }
    public ApplicationUser Followees { get; set; }

    [Key]
    [Column(Order = 1)]
    public string FollowerId { get; set; }

    [Key]
    [Column(Order = 2)]
    public string FolloweeId { get; set; }
}

and this is code of the fluent api to build the data :

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Following> Followings { get; set; }

    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ApplicationUser>()
                    .HasMany(u => u.Followers)
                    .WithRequired(f => f.Followees)
                    .WillCascadeOnDelete(false);

        modelBuilder.Entity<ApplicationUser>()
                    .HasMany(u => u.Followees)
                    .WithRequired(f => f.Followers)
                    .WillCascadeOnDelete(false);

        base.OnModelCreating(modelBuilder);
    }
}

This is the controller for the following api :

public class FollowingsController : ApiController
{
    private readonly ApplicationDbContext _context;

    public FollowingsController()
    {
        _context = new ApplicationDbContext();
    }

    [HttpPost]
    public IHttpActionResult Follow(FollowingDto dto)
    {
        var userId = User.Identity.GetUserId<string>();
        Console.WriteLine(userId);

        var exists = _context.Followings.Any(f => f.FollowerId == userId && f.FolloweeId == dto.FolloweeId);

        if (exists) 
             return BadRequest("The following already exists");

        var following = new Following
                            {
                                FollowerId = userId,
                                FolloweeId = dto.FolloweeId
                            };

        _context.Followings.Add(following);
        _context.SaveChanges();

        return Ok();
    }
}

This is the dto object :

public class FollowingDto
{
    public string FolloweeId { get; set; }
}

This is the frontend code for the homepage where the following button is located:

@model Gighub.ViewModels.GigsViewModel

@{
    ViewBag.Title = "Home Page";
}

<ul class="gigs">
    @foreach (var gig in Model.UpComingGigs)
    {
    <li>
     
        <div class="details">
            <span class="artist">
                @gig.Artist.Name @if (Model.ShowActions)   {
                     <button class="btn btn-link btn-sm js-toggle-following" data-user-id="@gig.ArtistId">Follow?</button>
                }
            </span>
        </div>
    </li>
    }
</ul>

@section scripts {
    <script>
        $(document).ready(function () {

                $(".js-toggle-following").click(function (e) {
                    var btn = $(e.target);
                    $.post("/api/followings", { "FolloweeId": btn.attr("data-user-id") })
                    .done(function () {
                        btn.text("following")
                    })
                    .fail(function () {
                        alert("something failed");
                    });
                });
            });
    </script>
    }

and here is applicationuser class :

    public class ApplicationUser : IdentityUser
    {
        public ICollection<Following> Followers { get; set; }
        public ICollection<Following> Followees { get; set; }
        public ApplicationUser()
        {
            Followers = new Collection<Following>();
            Followees = new Collection<Following>();
        }
        [Required]
        [StringLength(100)]
        public string Name { 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;
        }
    }
ibrahim
  • 135
  • 3
  • 9

2 Answers2

0

Replace your db context with this:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
       modelBuilder.Entity<Following>(entity =>
            {
               
                entity.HasOne(d => d.Followee )
                    .WithMany(p => p.Followees )
                    .HasForeignKey(d => d.FolloweeId)
                    .OnDelete(DeleteBehavior.ClientSetNull);

                entity.HasOne(d => d.Follower )
                    .WithMany(p => p.Followers )
                    .HasForeignKey(d => d.FollowerId );
            });
        base.OnModelCreating(modelBuilder);
    }

and check your db. Maybe you will need to re-migrate.

Serge
  • 40,935
  • 4
  • 18
  • 45
0

From the exception, the issue looks to be on your ApplicationUser class and mapping the followings. If this is EF6 or EF Core 5 you can have something like:

public class ApplicationUser
{
   // ...

   public virtual ICollection<ApplicationUser> Followers { get; set; } = new List<ApplicationUser>();
   public virtual ICollection<ApplicationUser> Followees { get; set; } = new List<ApplicationUser>();
}

This relationship has to be explicitly mapped: (EF6)

modelBuilder.Entity<ApplicationUser>()
    .HasMany(u => u.Followers)
    .WithMany(u => u.Followees)
    .Map(u => u.ToTable("Followings")
        .MapLeftKey("FollowerId")
        .MapRightKey("FolloweeId"))
    .WillCascadeOnDelete(false);

EF Core's mapping will be slightly different but should be able to follow the same gist, just need to check up on EF Core 5's many-to-many setup supporting non-declared linking entities.

Alternatively you can map it out with a "Following" entity, (required for EF Core < 5) but this changes the relationship somewhat because it acts more like a Many-to-One-to-Many rather than Many-to-Many:

public class ApplicationUser
{
   // ...

   public virtual ICollection<Following> FollowingsAsFollower { get; set; } = new List<Following>();
   public virtual ICollection<Following> FollowingsAsFollowee { get; set; } = new List<Following>();
}

public class Following
{
    [Key, Column(Order=0), ForeignKey("Follower")]
    public int FollowerId {get; set;}
    [Key, Column(Order=1), ForeignKey("Followee")]
    public int FolloweeId {get; set;}

    public virtual ApplicationUser Follower { get; set; }
    public virtual ApplicationUser Followee { get; set; }
}

The mapping then looks like:

modelBuilder.Entity<Following>()
    .HasRequired(u => u.Follower)
    .WithMany(u => u.FollowingsAsFollower)
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Following>()
    .HasRequired(u => u.Followee)
    .WithMany(u => u.FollowingsAsFollowee)
    .WillCascadeOnDelete(false);

This looks rather odd with the linking table since we are mapping a many-to-many self-referencing. I mixed the configuration between attribute and modelBuilder but you will need at least some explicit modelBuilder/EntityTypeConfiguration mapping for the relationships because of the self-referencing types. EF's conventions for automatically mapping relationships are based on the navigation property type rather than the property names, so setting up multiple relationships for the same type need to be explicit.

The issue will likely be trying to set up a single Following in the user for Followers/Followees. There needs to be two collections for this self-referencing relationship because the "Following" table is expecting a direct relationship between the FollowerID on it's end to marry to the Follower IDs in a collection on the other end. A many to many relationship set up between two tables will have collections on each table so that a Student will list all Courses and a Course will list all Students. With a self-referencing table the same thing applies. A Follower needs to list all Followees, and a Followee needs to list all Followers. It won't work if it's expecting to resolve a collection where I might be a Followee or Follower. Alternatively you can always do away with the collection on Application user and just have a 1-way many-to-one on Following to go to and resolve the applicable users.

Steve Py
  • 26,149
  • 3
  • 25
  • 43