ASP.NET Web API with Many-Many relationships - asp.net

I'm having some trouble with ASP.NET Web API with many-many relationships between models. Here are my models (which I've simplified for brevity):
public class Model1
{
public int Model1ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Model2> Model2s{ get; set; }
public string Self
{
get
{
return string.Format(CultureInfo.CurrentCulture,
"api/model1/{0}", this.Model1ID);
}
set { }
}
}
public class Model2
{
public int Model2ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Model1> Model1s{ get; set; }
public string Self
{
get
{
return string.Format(CultureInfo.CurrentCulture,
"api/model2/{0}", this.Model2ID);
}
set { }
}
}
and my relevant Model1 API controller excerpt:
public class Model1sController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Model1s
public IQueryable<Model1> GetModel1s()
{
return db.Model1s;
}
...
}
When I navigate to /api/model1s I get a long JSON nested error, here is the innermost Exception message.
There is already an open DataReader associated with this Command which must be closed first.
What I'm trying to achieve is output like this, but I cannot figure out how to get it working.
[{
"Model1ID": 1,
"Name": "Some model 2 name",
"Model2s": [{
"Model2ID": 1,
"Name": "Another model 2 name"
}, {
"Model2ID": 2,
"Name": "Some model 2 name"
}]
}, {
"Model1ID": 2,
"Name": "Another model 1 name",
"Model2s": [{
"Model2ID": 2,
"Name": "Some model 2 name"
}]
}]

What you need is called and associative entity, some devs call them a lookup table. An associative entity will hold the “association” between two other entities. In your case I believe that there is a scaffolding engine that will build the database tables for you based on the classes you create. Someone else may be able to speak to how the scaffolding engine works.
I would create a class called “TvProgramming” and give it properties Name, Id, Host_Id, Host_Name, and List. Now with this set up you can have as many hosts and as many tv shows as you want and still create unique programming schedules.
Adjust the tv show and host objects so that they only have properties that are unique to themselves ie a TvShow will have an name, id, and maybe a length. A host may have name, id, network, and location info however notice that the host object and tv show object have no knowledge of the other, only the associative entity holds knowledge of the relationship between them.
At the end of the day what your api should return is a set of TvProgramming objects that contain the hosts and for each host a list of tv shows… here is an quick example of the class structure I’m talking about, you’ll have to tweak it a bit to fit your needs but it should get started.
namespace YourProjectName.Models
{
public class TvShow
{
public int id { get; set; }
public string name { get; set; }
public TimeSpan length { get; set; }
public string rating { }
public TvShow() { }
}
public class Host
{
public int id { get; set; }
public string name { get; set; }
public string network { get; set; }
public string city { get; set; }
public string state { get; set; }
public string zip { get; set; }
public string country { get; set; }
public Host() { }
}
public class TvProgramming
{
public int id { get; set; }
public string name { get; set; }
public int host_Id { get; set; }
public string host_Name { get; set; }
public List<TvShow> shows { get; set; }
public TvProgramming()
{
this.shows = new List<TvShow>();
}
}
}

As a way of possibly preventing the error you are getting, try modifying your Controller code like this:
public class Model1sController : ApiController
{
// GET: api/Model1s
public IQueryable<Model1> GetModel1s()
{
using (var db = new ApplicationDbContext())
{
return db.Model1s;
}
}
}

Related

how I can send 2 object to my API (ASP.NET)?

I have a problem that I cannot solve. I would like to transmit the deck created by the user to my API but the problem is that the card and the deck are two different entities in my database that's why I need to pass the information of the deck and the list card to my API to add them to my database.
my entity:
public class Card
{
public Card() {
this.Decks = new HashSet<Deck>();
}
public int Id { get; set; }
[Column(TypeName = "json")]
public string Content { get; set; }
[NotMapped]
public virtual ICollection<Deck> Decks { get; set; }
}
public class Deck
{
public Deck() {
this.Cards = new HashSet<Card>();
}
public int Id { get; set; }
public string Name { get; set; }
[DataType(DataType.Date)]
public DateTime CreateAt { get; set; }
public User User { get; set; }
[NotMapped]
public virtual ICollection<Card> Cards { get; set; }
}
public class Join
{
public int DeckId { get; set; }
public Deck Deck { get; set; }
public int CardId { get; set; }
public Card Card { get; set; }
}
my API:
[HttpPost]
public void Add ([FromBody] JsonObject request) {
}
the JSON:
{
"Deck":{
"Name": "",
"CreateAt": "2007-07-15",
"User": "null"
},
"Crads":[
{"content": {}},
{"content": {}}
]
}
Response:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|99e854f-4f63859a690203b6.",
"errors": {
"$.Deck.User": [
"The JSON value could not be converted to MTG_Deck.Models.User. Path: $.Deck.User | LineNumber: 4 | BytePositionInLine: 22."
]
}
}
I think it is better that in your API you receive a DTO that encapsulates the two classes you need (Deck and Card). Inside your method then take the information you need and save the entities correctly in the database.
public class AddDeckAndCardDTO
{
public Deck Deck { get; set; }
public List<Card> Card { get; set; }
}
And the method Add in the API
[HttpPost]
public void Add ([FromBody] AddDeckAndCardDTO request)
{
var card = request.Card;
var deck = request.Deck
// the code to mapped entities
// the code to save your entities
}

JsonConvert.DeserializeObject<> is returning null last childtoken of the two childrentokens

I am sending a JObject from the frontend to my API, which is divided into First and Last childtokens, as seen in the picture below:
However, when I am trying to use the following code, the last part of childrendtoken is becoming null
var RVoucher = JsonConvert.DeserializeObject<VMReceive>(request.ToString());
This is what I am having in the debugging mode:
Here, the VMReceive is a viewModel that consists of another viewmodel "VMMonth"and an ado.net generated model class "ReceiveVoucher".
Code of the models are given below:
public class VMReceive
{
public List<VMMonth> Month { get; set; }
public ReceiveVoucher receiveVoucher { get; set; }
}
public class VMMonth
{
public int item_id { get; set; }
public string item_text { get; set; }
}
public partial class ReceiveVoucher
{
public int ReceiveVoucherId { get; set; }
public Nullable<int> MonthId { get; set; }
public string ReceivedBy { get; set; }
public string Remarks { get; set; }
public Nullable<int> ReceivedAmount { get; set; }
}
I have also tried putting [JsonProperty("")] over each property of my "ReceiveVoucher" model class, but got the same 'null' issue.
I am not sure about what I am doing wrong here, your suggestion regarding this will be very helpful.
Your JSON property name doesn't match. Your class uses receiveVoucher whereas the JSON is ReceiveAmount. Also, why are you using JObject in the first place, this should work by just using the class name as the action parameter:
public HttpResponse PostReceive([FromBody] VMReceive RVoucher, int userId)
{
...
}

Entity Framework Core Query Specific Model both directions

Let me preface this question with, I am VERY new to ASP.NET Core/EF Core.
My model look like this:
namespace MyProject.Models
{
public class DeviceContext : DbContext
{
public DeviceContext(DbContextOptions<DeviceContext> options) : base(options) { }
public DbSet<Device> Devices { get; set; }
public DbSet<DeviceLocation> DeviceLocations { get; set; }
}
public class Device
{
public int Id { get; set; }
public string DeviceName { get; set; }
public string ServerName { get; set; }
public string MacAddress { get; set; }
public string LastUpdate { get; set; }
public string WiredIPAddress { get; set; }
public string WirelessIPAddress { get; set; }
public DeviceLocation DeviceLocation { get; set; }
}
public class DeviceLocation
{
public int Id { get; set; }
public string Location { get; set; }
public virtual ICollection<Device> Devices { get; set; }
}
}
I would like to be able to fetch a specific Device based on DeviceName, but I would also like to fetch ALL the devices in a particular Location.
I think the following would work for the first question:
var _Devices = DeviceContext.Devices.FirstOrDefault(d => d.DeviceName == "BLA");
I am just having a hard time getting the second query to run. Ideally, the output would be rendered to JSON to be consumed by an API. I would like the output to look something like this:
{
"Locations": {
"NYC": ["ABC", "123"],
"Boston": ["DEF", "456"],
"Chicago": ["GHI", "789"]
}
}
UPDATE
If I use the following code, it give me the following error:
Code:
// Grouping by ProfileName
var devices = DeviceContext.DeviceLocations.Include(n => n.Device).ToList();
var result = new { success = true, message = "Successfully fetched Devices", data = devices };
return JsonConvert.SerializeObject(result);
Error:
Additional information: Self referencing loop detected for property 'DeviceLocation' with type 'Project.Models.DeviceLocation'. Path 'data[0].Device[0]'.
You can try as shown below.
Note : Use Eager Loading with Include.
using System.Data.Entity;
var devicesList = DeviceContext.DeviceLocations.Where(d=>d.Location = "Your-Location-Name")
.Include(p => p.Devices)
.ToList();
Update :
var devicesList = DeviceContext.DeviceLocations
.Include(p => p.Devices)
.ToList();

Using ComplexType with ToList causes InvalidOperationException

I have this model
namespace ProjectTimer.Models
{
public class TimerContext : DbContext
{
public TimerContext()
: base("DefaultConnection")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectTimeSpan> TimeSpans { get; set; }
}
public class DomainBase
{
[Key]
public int Id { get; set; }
}
public class Project : DomainBase
{
public UserProfile User { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<ProjectTimeSpan> TimeSpans { get; set; }
}
[ComplexType]
public class ProjectTimeSpan
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public bool Active { get; set; }
}
}
When I try to use this action I get the exception The type 'ProjectTimer.Models.ProjectTimeSpan' has already been configured as an entity type. It cannot be reconfigured as a complex type.
public ActionResult Index()
{
using (var db = new TimerContext())
{
return View(db.Projects.ToList);
}
}
The view is using the model #model IList<ProjectTimer.Models.Project>
Can any one shine some light as to why this would be happening?
Your IList<ProjectTimeSpan> property is not supported by EF. A complex type must always be part of another entity type, you cannot use a complex type by itself. If you absolutely need to have ProjectTimeSpan as a complex type, you will need to create a dummy entity type that only contains a key and a ProjectTimeSpan, and change the type of Project.TimeSpans to a list of that new type.

Handling default values for models in EF4

I'm wondering what's the best way to handle default values for relationships when making models. (Specifically EF4)
For example, my Organization has a default Contact and I was wondering which one was the best approach. I got these two options (or any other anyone suggests if better)
Using Relationship:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
//Use a relationship for the default contact?
public Contact DefaultContact { get; set; }
}
Using Value:
public class Contact
{
public int Id { get; set; }
public string FirstName { get; set; }
//Use value?
public boolean IsDefault { get; set; }
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { get; set; }
}
I'd go with Option 1. While 2 is definitely easier to implement, it doesn't enforce rules such as "There cannot be 2 default contacts". I end up with something like the following:
public class Organization {
// ...
public virtual ICollection<Contact> { get;set; }
[ForeignKey("DefaultContactId")]
public Contact DefaultContact { get;set; }
public int? DefaultContactId { get;set; }
}
There's a limitation of this approach - it doesn't work nested deletes (see this question for more details). Because of this, you need to disable CascadeOnDelete for the 1-to-many relationship:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(co => co.Organization).WithMany().WillCascadeOnDelete(false);
}
(Code done without testing, but should work)
The other problem with this is that it's not possible to add the Default Contact at the same time as you're adding the organization, as EF can't figure out the correct order of statements. You need to call .SaveChanges between each. You can still use a TransactionScope to overcome this, but it's not clean:
using (var ts = new TransactionScope())
{
Organization org = new Organization
{
// ...
Contacts = new Collection<Contact>()
}
org.Contacts = new Contact() {};
orgRepo.SaveChanges();
// Now wire up the default contact
org.DefaultContact = org.Contacts.First();
orgRepo.SaveChanges();
}

Resources