Why my EF6 generated relationship not working? - entity-framework

I have two tables (Jobs and Versions) on a One to Many relationship in a PostgreSQL database. I have generated a DB Context and Models with dotnet-ef (EF6 Database-first) and created routes with JsonApiDotNetCore.
My two models :
// A Version (with one job)
[DisplayName("version")]
[Table("Versions")]
public partial class Version : Identifiable<long>
{
[Attr(PublicName = "id-version")]
public override long Id { get; set; }
[Attr(PublicName = "id-job")]
public long JobId { get; set; }
[Attr(PublicName = "name")]
public string Name { get; set; }
[Attr(PublicName = "job")]
public virtual Job JobIdNavigation { get; set; }
}
// A Job (with multiple Versions)
[DisplayName("job")]
[Table("Jobs")]
public partial class Job : Identifiable<long>
{
public Job()
{
this.Versions = new HashSet<Version>();
}
[Attr(PublicName = "id-job")]
public override long Id { get; set; }
[Attr(PublicName = "name")]
public string Name { get; set; }
[Attr(PublicName = "versions")]
public virtual ICollection<Version> Versions { get; set; }
}
And a DB Context :
public partial class TalendExplorerDbContext : DbContext
{
[...]
public virtual DbSet<Job> Jobs { get; set; }
public virtual DbSet<Version> Versions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("adminpack")
.HasPostgresExtension("ltree")
.HasAnnotation("Relational:Collation", "French_France.1252");
modelBuilder.Entity<Job>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("Jobs_pkey");
entity.Property(e => e.Id)
.HasColumnName("job_id")
.UseIdentityAlwaysColumn();
[...]
});
modelBuilder.Entity<Version>(entity =>
{
entity.HasKey(e => e.Id)
.HasName("Versions_pkey");
entity.Property(e => e.Id)
.HasColumnName("version_id")
.UseIdentityAlwaysColumn();
entity.Property(e => e.JobId).HasColumnName("job_id");
[...]
entity.HasOne<Job>(d => d.JobIdNavigation)
.WithMany(p => p.Versions)
.HasForeignKey(d => d.JobId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("Versions_id_job_fkey");
});
OnModelCreatingPartial(modelBuilder);
}
[...]
}
With those, I can get a resource like a Job but there is no Versions linked:
{
"links": {
"self": "https://localhost:5001/jobs/2"
},
"data": {
"type": "jobs",
"id": "2",
"attributes": {
"name": "job2",
"versions": []
},
"links": {
"self": "https://localhost:5001/jobs/2"
}
}
}
Is this normal behavior ? How can I make my relationship work ?
Edit 1
As suggest by Michael Mairegger, I try to include the relationship on the request:
from https://localhost:5001/jobs/2
to https://localhost:5001/jobs/2?include=versions
but got an error:
{
"errors": [
{
"id": "4c6d79c9-0af7-419b-b89c-d3a61588b73a",
"status": "400",
"title": "The specified include is invalid.",
"detail": "Relationship 'versions' does not exist on resource 'jobs'.",
"source": { "parameter": "include" }
}
]
}

I am not familiar with JsonApiDotNetCore but I think the reason is the same as in ODATA. The API does not load related data because you did not request it. Otherwise it can happen that you accidently load the whole database because every data is somehow connected to any other data.
The API supports an include query parameter where you can request the additional navigation properties. I think if you execute https://localhost:5001/jobs/2?include=versions the versions shall be in the results set.
See: Including Relationships for further information.

As suggest by a co-worker (and Michael Mairegger), I had to specify relationship with JsonApiDotNetCore.
So I need to change annotations on models:
// A Version (with one job)
[DisplayName("version")]
[Table("Versions")]
public partial class Version : Identifiable<long>
{
[...]
[HasOne(PublicName = "job")]
public virtual Job JobIdNavigation { get; set; }
}
// A Job (with multiple Versions)
[DisplayName("job")]
[Table("Jobs")]
public partial class Job : Identifiable<long>
{
[...]
[HasMany(PublicName = "versions")]
public virtual ICollection<Version> Versions { get; set; }
}
Now, the request https://localhost:5001/jobs/2 show a version (relationship is working! hooray!):
{
"links": {
"self": "https://localhost:5001/jobs/2"
},
"data": {
"type": "jobs",
"id": "2",
"attributes": {
"inserted-date": "2021-07-07T00:00:00+02:00",
"modification-date": null,
"path": "test",
"purpose": "purpose",
"description": "job de test",
"name": "job1",
"description-capture": null,
"purpose-capture": null
},
"relationships": {
"versions": {
"links": {
"self": "https://localhost:5001/jobs/2/relationships/versions",
"related": "https://localhost:5001/jobs/2/versions"
}
}
},
"links": {
"self": "https://localhost:5001/jobs/2"
}
}
}
Refer to Michael Mairegger anwser to get the content of related items via include keyword.

Related

How to return object references in ASP.net core Web API

I dont understand why getAllAssemblies() return referenced objects where "getAssembly(int id)" does not.
Can you give a heads up on how to have referenced objects returned from both methods?
The type:
public class Assembly
{
public int Id { get; init; }
public string? Name { get; set; }
public List<Object>? ChildrenObjects { get; set; }
[Required]
public int ParrentObjectId { get; set; }
[JsonIgnore]
public Assembly? ParrentObject { get; set; }
}
The data:
modelBuilder.Entity<Assembly>().HasData(
new Assembly() { Id = 10, Name = "1" },
new Assembly() { Id = 11, Name = "1.1", ParrentObjectId = 10
});
GetAssembly(int id)
[HttpGet("{id}")]
public async Task<ActionResult> GetAssembly(int id)
{
return Ok(await _objectsContext.Assemblies.FindAsync(id));
}
returns:
{
"id": 10,
"name": "1",
"childrenObjects": null,
"parrentObjectId": 0
}
GetAllAssemblies()
public async Task<ActionResult> GetAllAssemblies()
{
return Ok(await _objectsContext.Assemblies.ToListAsync());
}
returns:
[
{
"id": 10,
"name": "1",
"childrenObjects": [
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
],
"parrentObjectId": 0
},
{
"id": 11,
"name": "1.1",
"childrenObjects": null,
"parrentObjectId": 10
}
]

Multiple Reference Navigation properties to same table

In a EF Core model there are two classes, Company and Employee. 1 Company can have multiple Employees. I am wondering if I can add to reference navigation properties (collection of employees with different roles, i.e. manager role) referencing the same employees table and have EF Core figure out which employees to display in what property, depending on a property of the employee class (isManager, true/false).
See below, would this be possible?
public class Company
{
public int Id { get; set; }
public string Name { get; set; } = string.empty;
public virtual ICollection<Employee> Minions { get; set; }
public virtual ICollection<Employee> Managers { get; set; }
public Company()
{
Minions = new HashSet<Employee>();
Managers = new HashSet<Employee>();
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.empty;
public int CompanyId { get; set; }
public virtual Company Company { get; set; } = null!;
public bool IsManager { get; set; }
}
Id
Name
1
"MonkeyBizz Inc."
Id
Name
CompanyId
IsManager
1
"John Doe"
1
0
2
"Jane Doe"
1
0
3
"Skippy the Magnificent"
1
1
{
"id": 1,
"name": "MonkeyBizz Inc.",
"minions": [
{ "id": 1, "name": "John Doe", "companyId": 1, "isManager": false },
{ "id": 2, "name": "Jane Doe", "companyId": 1, "isManager": false }
],
"managers": [
{ "id": 3, "name": "Skippy the Magnificent", "companyId": 1, "isManager": true }
]
}

EF Core 2.2 - can't add new record with link to already existing another one

My POCO classes:
[Table]
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
public class CategoryProduct
{
public int CategoryId { get; set; }
public int ProductId { get; set; }
public Category Category { get; set; }
public Product Product { get; set; }
}
[Table]
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<CategoryProduct> CategoryProducts { get; set; }
}
Here's a function for inserting new records:
async Task CreateProduct(Product dto)
{
await ctx.Products.AddAsync(dto);
await ctx.SaveChangesAsync();
}
In dto I pass the following JSON:
{
"name": "Gräff Stettin",
"categoryProducts": [{
"categoryId": 1,
"productId": 1,
"category": {
"id": 1,
"name": "Drinks"
}
}, {
"categoryId": 2,
"productId": 1,
"category": {
"id": 2,
"name": "Alcohol"
}
}
]
}
As a result, at SaveChangesAsync() I get an exception with message regarding attempt to insert already existing Category. Tracing shows the following query:
INSERT INTO "Category" ("Id", "Name") VALUES (#p0, #p1);
How should I change my CreateProduct() method to avoid attempts to add categories with already existing categoryId?
you can use AutoMapper and ignore category =
var config = new MapperConfiguration(cfg => cfg.CreateMap<categoryProductsDto,categoryProducts>())
.ForMember(u => u.Category, options => options.Ignore());
and use mapper =
var mapper = config.CreateMapper();
categoryProducts entity = mapper.Map<categoryProducts>(input);
await _categoryProductsRepository.InsertAndGetIdAsync(entity);

I can't retrieve data from many to many relationship to my ViewModel

I have the Map table that relates to the Day table in a many-to-many relationship.
So, I created the MapDay table according to the EF documentation.
The tables that are not related from many to many, I return without problems...
This is my controller, and I'm using viewmodels...
[HttpGet("getmaps")]
public async Task<ActionResult<IEnumerable<MapViewModel>>> GetMap()
{
var maps = _mapper.Map<IEnumerable<MapViewModel>>(await _mapRepository.GetMaps());
if(maps.Count() > 0) return Ok(maps);
return NotFound();
}
This is my MapViewModel:
public class MapViewModel : MainViewModel
{
public Guid UserId { get; set; }
public double Lng { get; set; }
public double Lat { get; set; }
public AddressViewModel Address { get; set; }
/* EF Relations */
public ItemMapViewModel ItemMap { get; set; }
public IEnumerable<MapDayViewModel> MapDay { get; set; } //testing
public IEnumerable<DayViewModel> Day { get; set; } // need this?
}
And my repository with the query:
public async Task<IEnumerable<Map>> GetMaps()
{
return await Db.Maps.AsNoTracking()
.Include(i => i.Address)
.Include(it => it.ItemMap)
.Include(mp => mp.MapDay).ThenInclude(mp => mp.Day)
.ToListAsync();
}
This is my result JSON:
[
{
"userId": "705cbdaf-86e9-4759-8f85-4fa6f3560726",
"lng": 0.0,
"lat": 0.0,
"address": {
"street": "st 123",
"number": "12",
"apt": "34",
"area": "AreaT",
"zip": "123456789",
"city": "Belo ",
"state": "ST",
"id": "ba3e7a68-63eb-4383-b980-14dea9615072"
},
"itemMap": {
"id": "353ccb80-b9fd-4469-9270-6a399ad37201",
"item": "Item1"
},
"mapDay": [
{
"mapId": "719da65c-42c9-4954-a750-e0b90e82461e",
"dayId": "5b444e8e-642f-4175-9329-9ef4a0f7aa87"
}
],
"day": null,
"id": "719da65c-42c9-4954-a750-e0b90e82461e"
}
]
Using aspnet core 3.1
So, what can I do to return the days of this map?
Using aspnet core 3.1 So, what can I do to return the days of this
map?
If you want to include Day data in the returned results of IEnumerable<MapViewModel> type, you really need to add public IEnumerable<DayViewModel> Day {get; set;} to MapViewModel.
Since you did not provide your automapper code, I will complete your requirements based on the code you gave.
Here I will simplify your model as follows:
Models:
public class Map
{
public int MapId { get; set; }
public string MapName { get; set; }
public ICollection<MapDay> MapDay { get; set; }
}
public class Day
{
public int DayId { get; set; }
public string DayName { get; set; }
public ICollection<MapDay> MapDay { get; set; }
}
public class MapDay
{
public int MapId { get; set; }
public Map Map { get; set; }
public int DayId { get; set; }
public Day Day { get; set; }
}
ViewModels:
public class MapViewModel
{
public int MapId { get; set; }
public string MapName { get; set; }
public IEnumerable<MapDayViewModel> MapDay { get; set; } //testing
public IEnumerable<DayViewModel> Day { get; set; } // need this?
}
public class DayViewModel
{
public int DayId { get; set; }
public string DayName { get; set; }
}
public class MapDayViewModel
{
public int MapId { get; set; }
public int DayId { get; set; }
}
Here is the detailed Automapper code to convert data from Models to ViewModels:
[HttpGet("getmaps")]
public async Task<ActionResult<IEnumerable<MapViewModel>>> GetMap()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Map, MapViewModel>()
.ForMember(dest => dest.MapId, opt => opt.MapFrom(s => s.MapId))
.ForMember(dest => dest.MapName, opt => opt.MapFrom(s => s.MapName))
.ForMember(x => x.MapDay, opt => opt.MapFrom(model => model.MapDay))
.ForMember(x => x.Day, opt => opt.MapFrom(model => model.MapDay.Select(x => x.Day)));
cfg.CreateMap<MapDay, MapDayViewModel>()
.ForMember(dest => dest.MapId, opt => opt.MapFrom(src => src.MapId))
.ForMember(dest => dest.DayId, opt => opt.MapFrom(src => src.DayId));
cfg.CreateMap<Day, DayViewModel>()
.ForMember(dest => dest.DayId, opt => opt.MapFrom(src => src.DayId))
.ForMember(dest => dest.DayName, opt => opt.MapFrom(src => src.DayName));
});
IMapper _mapper = config.CreateMapper();
var maps = _mapper.Map<IEnumerable<MapViewModel>>(await _mapRepository.GetMaps());
if (maps.Count() > 0)
return Ok(maps);
return NotFound();
}
Here is the test result:

how to insert Embedded object in mongodb using Mvc core

I am trying to insert and update and delete (CRUD) an embedded object into existing Monogodb document,
assuming adding multiple addresses for a customer,
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"contact": "987654321",
"dob": "01-01-1991",
"name": "Tom Benzamin",
"address": [
{
"building": "22 A, Indiana Apt",
"pincode": 123456,
"city": "Los Angeles",
"state": "California"
},
{
"building": "170 A, Acropolis Apt",
"pincode": 456789,
"city": "Chicago",
"state": "Illinois"
}
]
}
I can do it using command line (adding multiple address) , but through mvc core and I can only insert single address while inserting the document using InserOne, also I can update it using ReplaceOne , but I can add the second or third address.
here is my c# class and Interface (Service)
public class Customer
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("contact")]
[Required]
public string Contact { get; set; }
[BsonElement("dob")]
[Required]
public string Dob { get; set; }
[BsonElement("name")]
public string Name { get; set; }
public Addresses Address { get; set; }
}
public class Addresses
{
public string Building { get; set; }
public string Pincode { get; set; }
public string City { get; set; }
public string State { get; set; }
}
}
and the service
public Customer Create(Customer c)
{
customers.InsertOne(c);
return c;
}
public void Update(string id, Customer c)
{
customers.ReplaceOne(cutomer => cutomer.Id == id, c);
}