Having som major problem with many to many relation.
public class Team
{
public Team()
{
Users = new HashSet<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Teams = new HashSet<Team>();
}
public int Id { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Cell { get; set; }
public ICollection<Team> Teams { get; set; }
}
After Returning a newly added Team like this
var currentUser = _ctx.Users.Where(u => u.Username == HttpContext.Current.User.Identity.Name).SingleOrDefault();
teamToAdd.Users.Add(currentUser);
var teamAdded = _ctx.Teams.Add(teamToAdd);
Save();
return teamAdded;
I get the following error in the response inner exception:
"Self referencing loop detected with type 'MatchMaker.Data.Team'. Path 'Users[0].Teams'."
There is obviously a circular reference going on but i want a Team to be able to have many Users and a User to be able to have many Teams. Is there any way of getting past this withouth creating DTOs?
Related
I'm doing some practice with Entity Framework and I want to create a web-api backend, which is able to manage requests by interacting with a MySql database and by responding with JSON strings in the message body of the replies. I'm quite new to it and I'm trying to learn it solving problem by problem in the project.
An example of the working code at the moment is:
public string getFilmById(int id)
{
return JsonSerializer.Serialize(ctx.Films.ToList().Where(filmToGet => filmToGet.Id == id));
}
Which generates the string:
[{"Id":7,"Title":"Dune","Genre":"Sci-fi","Duration":155,"Direction":[],"Interpretation":[]}]
Now, what I want to do is to include in the string the directors and the actors of the film. I've created all the models of the backend with a database-first approach, by importing and converting MySql tables into classes and by generating the dbcontext class automatically. This implies that I also have the Direction class to link Film and Director, because the relationship is Many to Many (the same is for Interpretation and Actor). Here the code:
public partial class Film
{
public Film()
{
Direction = new HashSet<Direction>();
Interpretation = new HashSet<Interpretation>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Genre { get; set; }
public int? Duration { get; set; }
public virtual ICollection<Direction> Directions { get; set; }
public virtual ICollection<Interpretation> Interpretations { get; set; }
}
public partial class Direction
{
public int Id { get; set; }
public int? IdDirector { get; set; }
public int? IdFilm { get; set; }
public virtual Film IdFilmNavigation { get; set; }
public virtual Director IdDirectorNavigation { get; set; }
}
public partial class Director
{
public Director()
{
Directions = new HashSet<Direction>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public virtual ICollection<Direction> Directions { get; set; }
}
In the end: how can I include Directors and Actors in the Film instance and make them appear in that json string? Technically this is a simple JOIN after all, but I don't really know how to do it using these classes.
Thank you for the help!
Here's the repo with all the code if can help:
https://github.com/marco-savino/film_archive_project.git
I'm currently working on the Asp.Net Core MVC project and have the following Message class
public class Message
{
public int Id { get; set; }
public string SenderId { get; set; }
[ForeignKey("SenderId")]
public virtual User Sender { get; set; }
public string RecipientId { get; set; }
[ForeignKey("RecipientId")]
public virtual User Recipient { get; set; }
public string Content { get; set; }
public bool IsRead { get; set; }
public DateTime? DateRead { get; set; }
public DateTime MessageSent { get; set; }
public bool SenderDeleted { get; set; }
public bool RecipientDeleted { get; set; }
}
Using the mapper, I get the below ViewModel for the Message review:
public class MessageReviewViewModel
{
public int Id { get; set; }
public string SenderId { get; set; }
[ForeignKey("SenderId")]
public virtual User Sender { get; set; }
public string RecipientId { get; set; }
[ForeignKey("RecipientId")]
public virtual User Recipient { get; set; }
public string Content { get; set; }
public bool IsRead { get; set; }
public DateTime? DateRead { get; set; }
public DateTime MessageSent { get; set; }
}
Now, I would like to get actually the Recipient and Sender users, however the reason I have such a simple MessageReviewViewModel is because I'm getting the list of this view models and with this users json throws an error & without it I have successfull result.
However, the problem is, that for the proper display in the Inbox view I still need the certain properties of Recipient and Sender user's (their main photo url, username & etc).
My mapper configuration is as below to get the messages from the repository:
public async Task<IEnumerable<MessageReviewViewModel>> GetMessagesForUserByUserId(string userId)
{
var messages = await messageRepository.GetMessagesForUser(userId);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Message, MessageReviewViewModel>();
cfg.IgnoreUnmapped();
});
IMapper mapper = config.CreateMapper();
var messageList = mapper.Map<IEnumerable<Message>, IEnumerable<MessageReviewViewModel>>(messages);
return messageList;
}
In the react component (I have integrated react.js in the asp.net core mvc) once it did mount I make a get request to get the messages list as below and setState messagesList to the received array.
componentDidMount() {
const xhr = new XMLHttpRequest();
xhr.open('get', "/Messages/GetMessages", true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ messagesList: data });
console.log(this.state.messagesList);
};
xhr.send();
}
And this is the action in the controller, that is being called:
[HttpGet]
public async Task<IActionResult> GetMessages()
{
var userFromRepo = await userManager.FindByNameAsync(User.Identity.Name);
var messages = await messagesService.GetMessagesForUserByUserId(userFromRepo.Id);
var sortedMessageList = messages.OrderByDescending(m => m.MessageSent);
return Json(sortedMessageList);
}
As I mentioned, it all works without any problem unless there are no virtual User Sender and virtual User Recipient in the MessageReviewViewModel. Once I have them in code, this is the error I get:
Probably it is worth to mention, that the User class objects (in my scenario Sender & Recipient for example) also have the virtual properties inside and I thought that it may be problem for Json parse these objects, which have other objects as property.
Could you please advise how I can include these properties in the ViewModel so neither mapper nor JSON throw any error? I'm okay even to get only selected properties of the obje (for example just string username, mainphoto url & etc).
It is also ok, if there is any Json method, that will solve this parseError with virtual users included in the ViewModel
I figured it out after finding that mapper can also be configured to the level that is required by the user
The Message model is as below as previously
public class Message
{
public int Id { get; set; }
public string SenderId { get; set; }
[ForeignKey("SenderId")]
public virtual User Sender { get; set; }
public string RecipientId { get; set; }
[ForeignKey("RecipientId")]
public virtual User Recipient { get; set; }
public string Content { get; set; }
public bool IsRead { get; set; }
public DateTime? DateRead { get; set; }
public DateTime MessageSent { get; set; }
public bool SenderDeleted { get; set; }
public bool RecipientDeleted { get; set; }
}
And the MessageReviewViewModel changed to below:
public class MessageReviewViewModel
{
public int Id { get; set; }
public string SenderId { get; set; }
public string SenderUsername { get; set; }
public string SenderMainPhotoUrl { get; set; }
public string RecipientId { get; set; }
[ForeignKey("RecipientId")]
public string RecipientUsername { get; set; }
public string RecipientMainPhotoUrl { get; set; }
public string Content { get; set; }
public bool IsRead { get; set; }
public DateTime? DateRead { get; set; }
public DateTime MessageSent { get; set; }
}
Only for the mapper the configuration was required as below:
public async Task<IEnumerable<MessageReviewViewModel>> GetMessagesForUserByUserId(string userId)
{
var messages = await messageRepository.GetMessagesForUser(userId);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Message, MessageReviewViewModel>()
.ForMember(destination=>destination.RecipientUsername,map=>map.MapFrom(
source=>source.Recipient.UserName)) //gets only string for username instead of whole User model
.ForMember(destination=> destination.RecipientMainPhotoUrl,map=>map.MapFrom(
source=>source.Recipient.MainProfilePicture)) //gets only string for profile picture instead of whole User model
.ForMember(destination=>destination.SenderUsername,map=>map.MapFrom(
source=>source.Sender.UserName))
.ForMember(destination=>destination.SenderMainPhotoUrl,map=>map.MapFrom(
source=>source.Sender.MainProfilePicture));
cfg.IgnoreUnmapped();
});
IMapper mapper = config.CreateMapper();
var messageList = mapper.Map<IEnumerable<Message>, IEnumerable<MessageReviewViewModel>>(messages);
return messageList;
}
As all received data now are only strings, Json has no problem parsing it
I am using the Sakila Sample Database from MySql on a MySql server. The Diagram looks as follows.
The important tables are the store, inventory and film tables. The is a many-to-many relationship between the tables and the linker table is the inventory table.
I scaffolded this Database in a new dotnetcore project using EFCore 2.
I am trying to get a list of stores and their list of films.
The Entities are defined as follows:
Store
public class Store
{
public Store()
{
Customer = new HashSet<Customer>();
Inventory = new HashSet<Inventory>();
Staff = new HashSet<Staff>();
}
public byte StoreId { get; set; }
public byte ManagerStaffId { get; set; }
public short AddressId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Address Address { get; set; }
public Staff ManagerStaff { get; set; }
public ICollection<Customer> Customer { get; set; }
public ICollection<Inventory> Inventory { get; set; }
public ICollection<Staff> Staff { get; set; }
}
Inventory
public partial class Inventory
{
public Inventory()
{
Rental = new HashSet<Rental>();
}
public int InventoryId { get; set; }
public short FilmId { get; set; }
public byte StoreId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Film Film { get; set; }
public Store Store { get; set; }
public ICollection<Rental> Rental { get; set; }
}
Film
public partial class Film
{
public Film()
{
FilmActor = new HashSet<FilmActor>();
FilmCategory = new HashSet<FilmCategory>();
Inventory = new HashSet<Inventory>();
}
public short FilmId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public short? ReleaseYear { get; set; }
public byte LanguageId { get; set; }
public byte? OriginalLanguageId { get; set; }
public byte RentalDuration { get; set; }
public decimal RentalRate { get; set; }
public short? Length { get; set; }
public decimal ReplacementCost { get; set; }
public string Rating { get; set; }
public string SpecialFeatures { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Language Language { get; set;
public Language OriginalLanguage { get; set; }
public ICollection<FilmActor> FilmActor { get; set; }
public ICollection<FilmCategory> FilmCategory { get; set; }
public ICollection<Inventory> Inventory { get; set; }
}
My context looks as follows:
modelBuilder.Entity<Inventory>(entity =>
{
entity.ToTable("inventory", "sakila");
entity.HasIndex(e => e.FilmId)
.HasName("idx_fk_film_id");
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
And lastly the repo looks as follows:
public IEnumerable<Store> GetStores()
{
return _context.Store.
Include(a => a.Inventory).
ToList();
}
Problem:
When I call this method from a Controller to get the list of stores I don´t get any json response on Postman. Yet if I debug into the list that is returned from the Controller I find the list of stores.
The problem is that the list contains:
store->inventory->film->store->inventory->film->store... Etc. Creating a circular dependency that fills up the allowed Process memory of the request.
Possible Solutions:
I think it has to do with the fact that on the Context both the Foreign Keys are defined as HasIndex instead of HasKey
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
When I define it as HasKey then I get an Error:
'The relationship from 'Rental.Inventory' to 'Inventory.Rental' with
foreign key properties {'InventoryId' : int} cannot target the primary
key {'StoreId' : byte, 'FilmId' : short} because it is not compatible.
Configure a principal key or a set of compatible foreign key
properties for this relationship.'
To answer #hamzas comment, I did find a solution to this problem. I used EFCore to build the entities and the DBContext through scaffolding (DB First). As a best practice you should be using Models (Dtos) to represent the Data for the client. EFCore is very helpful in giving us the flexibility to access this M to N relationship however we want. This gives us the flexibility to represent this Data to the client however we want.
Whatever your use case might be. You have to convert the M to N relationship into an 1 to N model.
Use Case #1: You want to show all the movies for a specific store.
Solution
Step #1: You create a StoreDto (Model)
public class StoreDto
{
int StoreId { get; set; }
ICollection<FilmDto> Films { get; set; }
= new List<FilmDto> ();
}
Step #2: Create a FilmDto
public class FilmDto
{
int FilmId { get; set; }
int StoreId { get; set; }
string FilmName { get; set; }
}
Step #3: You provide a Mapping with auto mapper
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<Store, StoreDto>();
CreateMap<Film, FilmDto>();
}
}
Step #4: Query the data correctly, Unfortunately I don´t have this example anymore to test this code, so here is where you´ll have to experiment a bit
public Store GetFilmsForStore(byte StoreId)
{
return _context.Store.
Include(a => a.Inventory).
ThenInclude(i => i.Film)
ToList();
}
On the "Include" part you want to only get the Inventory entries where StoreId == Inverntory.StoreId and then Include the Films Object from the resulting list.
I hope you get the jist of it. You want to break up your m to n relationships and make them seem like 1 to m for your clients.
I'm building REST API server in .NET core. I'm testing my code via Postman software. I have a problem with Include() method that enables me to attach navigation property data. I'm trying to get data in [HttpGet] action and objects that are being returned are wrong.
My code :
MODELS
Session model
public class Session
{
[Key]
public int IDSession { get; set; }
[Required]
public DateTime LogInTime { get; set; }
public DateTime LogOutTime { get; set; }
[Required]
public int IDUser { get; set; }
public User User { get; set; }
[Required]
public int IDMachine { get; set; }
public Machine Machine { get; set; }
}
User model
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Forename { get; set; }
[Required]
public string Name { get; set; }
public string AvatarPath { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public List<UserGroup> UsersGroups { get; set; }
public List<Alarm> ExecutedAlarms { get; set; }
public List<Alarm> ResetedAlarms { get; set; }
public List<AccessCard> Cards { get; set; }
public List<AccessCard> UserCardsAdded { get; set; }
public List<User> UsersAdded { get; set; }
public List<Session> Sessions { get; set; }
public List<EventsLog> Events { get; set; }
public List<Reference> References { get; set; }
public List<UserPermission> UsersPermissions { get; set; }
}
Session controller
[Produces("application/json")]
[Route("api/Sessions")]
public class SessionsController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public SessionsController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/sessions
[HttpGet]
public async Task<IActionResult> GetSessions()
{
var sessions = await _context.Sessions.Include(s => s.User). ToListAsync();
if (sessions.Any())
{
return new ObjectResult(sessions);
}
else
{
return NotFound();
}
}
// GET: api/sessions/1
[HttpGet("{id}", Name = "GetSessionByID")]
public async Task<IActionResult> GetSessionByID(Int32 id)
{
var session = await _context.Sessions.Include(s => s.User).FirstOrDefaultAsync(s => s.IDSession == id);
if (session == null)
{
return NotFound();
}
else
{
return new ObjectResult(session);
}
}
#endregion
}
The idea is that User model contains List<Session> collection that he/she created. I want to be able to return users with its sessions
Of course Session model contains a single User because every session is related with a specific, single User.
Now, when I need to get all sessions objects in SessionController with GetSessions() or GetSessionsByID() I use POSTMAN [HttpGet] action like this : http://localhost:8080/api/sessions which returns me wrong data :
A session contains a user and in turn a single user is related with its sessions. It looks like it tries to return me Session object properly, includes User object but then it tries to include all sessions for that User. That's not what I want. It looks like some kind of a loop. Sessions shoud be returned with its User objects and that's it. How can I achieve that ? Am I doing some logical mistake in my models ?
Thanks !
I met also this issue recently. So, I've fixed it by adding this script in the Startup.cs file and ConfigureServices method :
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
So, you suffix services.AddMvc() by this code who means that you have to make JSON.Net to ignore cycles finded to the nested object request. And of course having Newtonsoft.Json package installed to your project and referenced in each concerned file
For much clearer information, see this link at Related Data and Serialization section :
https://learn.microsoft.com/en-us/ef/core/querying/related-data
Hope this is helpfull for you
I'm developing a prototype platform using MVC in which users can make a profile and use that profile to make text posts, like a social media site. I have the following two tables in my database:
Profiles
public partial class Profile
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Profile()
{
this.Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Country_ { get; set; }
public System.DateTime DoB { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Post> Posts { get; set; }
}
}
Posts
public partial class Post
{
public int Id { get; set; }
public string Text { get; set; }
public System.DateTime DATE { get; set; }
public int ProfileId { get; set; }
public virtual Profile Profile { get; set; }
}
}
In my ViewModel I have a list of Posts called PostList, which I want to populate with all of the Post records made by the user. So, I need to populate the list with all records where the ProfileId in Posts is equal to the Id of Profile, which is determined by whether or not the UserId in Profile is equal to the current user's Identity.
In short, I need:
Post List = Posts where ProfileId = Profiles.Id Where Profiles.UserId = CurrentUserId.
Any ideas? I've tried the following, but it's totally wrong:
var userId = User.Identity.GetUserId();
ViewModels.ProfileViewModel pVm = new ViewModels.ProfileViewModel();
pVm.PostList = db.Posts.Include(db.Profiles).Where(a => a.UserId == userId).ToList();
pVm.UserName = User.Identity.GetUserName();
return View(pVm);
You just walk the relationship in your query:
pVm.PostList = db.Posts.Where(a => a.Profile.UserId == userId);
It's unnecessary to use Include since Profile will automatically be joined to make the query.