RavenDB TransformResult: Assign properties of a document to each elements of a collection - ravendb

I want to transform some RavenDB Documents to a view model which is more suitable for the UI. The ContractorModel Object should look as follows:
public class ContractorModel
{
public string Identifier {get; set;}
public TrustcenterModel[] {get; set; }
}
public class TrustcenterModel
{
public string Ean {get; set;}
public string Name {get; set;}
}
The corresponding json docs are in the following format:
ContractorJson:
{
"Identifier": "42",
"Trustcenters": [
{
"Ean": "2222222222222",
"ValidFrom": "2016-01-13T00:00:00.0000000",
"ValidTo": "2499-12-31T00:00:00.0000000"
}
]
}
TrustcenterJson:
{
"Ean": "2222222222222",
"Name": "FooBar",
}
When I query a Contractor with the subsequent Transformer, I already get the related Trustcenter documents, but I'm missing the two properties ValidFrom and ValidTo. How can I project these two properties from the contractor to each trustcenter?
this.TransformResults = contractors =>
from contractor in contractors
select new
{
contractor.Identifier,
Trustcenters = contractor.Trustcenters.Select(x => this.LoadDocument<TrustCenter>(TrustCenter.IdPrefix + x.Ean)),
};

It should be something like this:
this.TransformResults = contractors =>
from contractor in contractors
select new
{
contractor.Identifier,
Trustcenters = contractor.Trustcenters.Select(x => new
{
x.Ean,
x.ValidFrom,
x.ValidTo,
Name = this.LoadDocument<TrustCenter>(TrustCenter.IdPrefix + x.Ean).Name
}),
};

Related

How to return a List from nested lists with LINQ?

I have to return all rooms which doesn't contain Cat or Owl. If a room contains even one forbidden animal, it cannot be added to the list which we'd like to return.
It's going to be a method for a service which is used as an API endpoint.
public async Task<List<Room>> GetRoomForRabbitOwners()
{
}
public enum PetType : byte
{
        Cat,
        None,
        Rabbit,
        Owl
}
Here is the data structure :
[
{
"id": 1,
"capacity": 5,
"residents": [
{
"id": 2,
"name": "Tom",
"houseType": 2,
"petType": 1,
"room": null
},
{
"id": 4,
"name": "Nicol",
"houseType": 2,
"petType": 3,
"room": null
}
]
},
{
"id": 2,
"capacity": 5,
"residents": [
{
"id": 3,
"name": "Rambo",
"houseType": 2,
"petType": 2,
"room": null
}
]
},
{
"id": 3,
"capacity": 1010,
"residents": []
},
{
"id": 4,
"capacity": 10,
"residents": []
},
{
"id": 5,
"capacity": 15,
"residents": []
}
]
A bunch of times, I managed to write an expression which seemed good but in the end it failed by returning a List of "Tenant" (class) , since only "Room" (class) allowed.
It seems like your post needs a little more information. I.e. what do you mean with tenant vs resident? Also your data structure defines a room as containing residents. Do you want the room returned w/out it's residents?
Here's a solution that returns a list of rooms w/out Cats & Owls:
void Main()
{
var data = File.ReadAllText(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"data.json"));
var allRooms = JsonConvert.DeserializeObject<List<Room>>(data);
var filteredRooms = GetRoomForRabbitOwners(allRooms).Dump();
}
public List<Room> GetRoomForRabbitOwners(List<Room> rooms)
{
return rooms
.Where(room => !room.Residents.Any(resident =>
(PetType)resident.PetType == PetType.Cat ||
(PetType)resident.PetType == PetType.Owl))
.ToList();
}
public enum PetType : byte
{
Cat = 1,
None = 2,
Rabbit = 3,
Owl = 4
}
public class Resident
{
public int Id { get; set; }
public string Name { get; set; }
public int HouseType { get; set; }
public byte PetType { get; set; }
public int? Room { get; set; }
}
public class Room
{
public int Id { get; set; }
public int Capacity { get; set; }
public List<Resident> Residents { get; set; }
}
So you have Rooms and Residents. There is a one to many relation between Rooms and Residents: Every Room has zero or more Residents, every Resident is the Pet in exactly one Room.
It is wise too separate your problem into two subproblems:
Read your source. Get a sequence of "Rooms with their zero or more Residents"
Give a sequence of Rooms with their Residents, give me all Rooms that have neither a Cat nor an Owl as Resident.
.
class Resident
{
public int Id {get; set;}
public string Name {get; set;}
public HouseType HouseType {get; set;}
public PetType PetType {get; set;}
... // etc
}
class Room
{
public int Id {get; set;}
public int Capacity {get; set;}
public virtual ICollection<Resident> Residents {get; set;}
}
I choose the Residents as a virtual ICollection<Resident> for several reasons. First of all, it is not a List, because Resident[2] doesn't have a defined meaning: you are not certain whether Cats come before Dogs. ICollection<...> contains all methods you need to handle the Residents: you can Add / Remover / Count Residents. The method is virtual, so procedures that will actual read your source to create the Rooms are free to decide what specific class they want to use to store the Residents (List<Resident> or Resident[...], or something else.
The following procedure will read your source into a sequence of "Rooms with their Residents":
public IEnumerable<Room> GetRoomsWithResidents()
{
...
}
You are probably more familiar with the data format that your sequence has, so you can implement it. The advantage of a separate procedure is, that it will be easy to unit test. Easy to change: if later you decide to use a database to store the Rooms, or a CSV-file. Changes will be minimal.
Requirement Give a sequence of Rooms, and a sequence of forbidden PetTypes, give me all Rooms that have no Residents of these PetTypes at all.
I'll write this as an extension method, so you can intertwine this with other LINQ methods. If you are not familiar with extension methods, consider to read Extension Methods demystified.
public IEnumerable<Room> ToRoomsWithoutForbiddenPets(this IEnumerable<Room> rooms,
IEnumerable<PetType> forbiddenPets)
{
// TODO: check input not null. Raise exceptions
return rooms.Where(room => !room.Residents
.Select(resident => resident.PetType)
.Intersect(forbiddenPets)
.Any());
}
In words: from every Room in the sequence of Rooms, keep only those Rooms that meet the following requirement: take the PetType from every Resident in the Room. This is a sequence of PetTypes. Intersect this sequence with the forbiddenPetTypes. Only keep the Room if this intersection does not have any elements (is empty).
Usage:
IEnumerable<PetType> forbiddenPets = new PetType[] {PetType.Cat, PetType.Owl}
IEnumerable<Room> validRooms = GetRoomsWithResidents()
.ToRoomsWithoutForbiddenPets(forbiddentPets);
The method ToRoomsWithoutForbiddenPets is also easy to understand, easy to unit test and easy to maintain.

Better Way of Dealing With Circular References? - EF Core 3.1 and Web API

I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.
Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.
User:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
DtoItems:
namespace TestWebApplication.Database.DTOs
{
public class DtoItems
{
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public DtoUser User { get; set; }
}
}
DtoUser:
namespace TestWebApplication.Database.DTOs
{
public class DtoUser
{
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
}
}
TestController:
[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
List<Items> items = _myContext.Items.Include(i => i.User).ToList();
// DTOs
List<DtoItems> dtoItems = new List<DtoItems>();
foreach (var i in items)
{
var dtoItem = new DtoItems
{
Item = i.Item,
Category = i.Category,
Type = i.Type,
User = new DtoUser
{
UserName = i.User.UserName,
Dob = i.User.Dob,
Location = i.User.Location
}
};
dtoItems.Add(dtoItem);
}
return Ok(dtoItems);
}
The output from endpoint:
[
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xx"
}
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xxx"
}
}
]
In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.
I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).
There is really only two other ways of handling this.
You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line
Like so :
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820
You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...
So your best bet, stick with the DTO.
More info :
https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
https://github.com/dotnet/runtime/issues/30820
https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

Entity Framework Core - Related products of type product

I'm using Entity Framework Core and Fluent API.
I'm trying to implement a relatedProducts array that is included in my product model like so:
{
"id": 13
"name": "something",
"heading": "something else",
"summary": "please put me out my misery!"
"relatedProducts": [
{
"name": "something related",
"heading": "something else related",
"summary": "something in summary"
},
{
"name": "something related",
"heading": "something else related",
"summary": "something in summary"
}
]
}
I currently have this in my Context:
modelBuilder.Entity<RelatedProduct>()
.HasKey(r => new { r.ProductId, r.RelatedId });
modelBuilder.Entity<RelatedProduct>()
.HasOne(p => p.Product)
.WithMany(r => r.RelatedProducts)
.HasForeignKey(p => p.ProductId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<RelatedProduct>()
.HasOne(p => p.Related)
.WithMany(r => r.ProductsRelated)
.HasForeignKey(p => p.RelatedId)
.OnDelete(DeleteBehavior.Restrict);
My Related Product class looks like this:
public class RelatedProduct
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int RelatedId { get; set; }
public virtual Product Related { get; set; }
}
The trouble is my relatedProduct array is just coming back empty even though I've linked up the Ids in the db.
Any assistance on this would be awesome as i don't want to end up 'Bodging' it!
You have to Include the related products (unless Lazy Loading is enabled) while you are querying. So you can make your query like as follows:
var product = _dbContext.Products.Include(p => p.RelatedProducts)
.ThenInclude(rp => rp.Related)
.FirstOrDefault(p => p.Id == productId);
Now your desired product will have its related products.
Better project your query as follows:
var product = _dbContext.Products.Select(p => new
{
p.Id,
p.Name,
p.Heading,
p.Summary
RelatedProducts = u.RelatedProducts.Select(rp => rp.Related).ToList()
}).FirstOrDefault(p => p.Id == productId);
Here product is anonymous type. If you want you can make it strongly typed by projecting the query to a ViewModel as follows:
public class ProductDetailsViewModel
{
public int Id {get; set;}
public string Name {get; set;}
public string Heading {get; set;}
public string Summary {get; set;}
public List<Product> RelatedProducts {get; set;}
}
Then in query:
var product = _dbContext.Products.Select(p => new ProductDetailsViewModel
{
Id =p.Id,
Name = p.Name,
Heading = p.Heading,
Summary = p.Summary
RelatedProducts = p.RelatedProducts.Select(rp => rp.Related).ToList()
}).FirstOrDefault(p => p.Id == productId);

Sorting on nested Id property

Let's say we have a document like this
public class Event
{
public string Id { get; set; }
public EntityDescriptor Venue { get; set; }
// Other properties omitted for simplicity
}
public class EntityDescriptor
{
public string Id { get; set; }
public string Name { get; set; }
}
And an index like this
public class Events : AbstractIndexCreationTask<Event>
{
public Events()
{
Map = items => from e in items
select new
{
Venue_Id = e.Venue.Id,
Venue_Name = e.Venue.Name
};
}
}
When trying to sort on Event.Venue.Id
session.Query<Event, Events>().Take(10).OrderBy(e => e.Venue.Id).ToArray();
the sent request is
/indexes/Events?&pageSize=10&sort=__document_id&SortHint-__document_id=String
Is this by design or a bug?
PS: OrderBy(e => e.Venue.Name) works as expected (sort=Venue_Name).
It's not a bug. __document_id is the special known field containing the ID of the document. It's there regardless of whether you have an .Id property.
edit
I misread your question. This indeed appears to be a bug. I recommend you send a simple repro case to the Raven forum and let them know which RavenDB version you're using.

Fluent Nhibernate many-to-many issue

I have three tables:
Person (Id, FirstName)
Organization (Id, Name)
PersonOrganization (PersonId, OrganizationId, Details) many-to-many table
When I first mapped this using Fluent NHibernate I did not have a details column in the PersonOrganization table and mapped this using HasManyToMany in the PersonMap and OrganizationMap (no need to create a PersonOrganization domain object or map). I could writethe following code:
Organization org = new Organization { Name = "org" };
People people = new People { FirstName = "firstname", Organization = org };
peopleRepository.Add(people); // ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
NHhibernate happily committed the data to all three tables.
The issue comes up when I added the details column in the PersonOrganization table. After some research it turns out that I now have to create a new PersonOrganization domain object and map and setup a HasMany relationship for both Person and Organization. My updated model and maps below:
public class People
{
public People()
{
LinkToOrganization = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual IList<PeopleOrganization> LinkToOrganization { get; set; }
}
public class Organization
{
public Organization()
{
LinkToPeople = new List<PeopleOrganization>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PeopleOrganization> LinkToPeople { get; set; }
}
public class PeopleOrganization
{
public virtual People People { get; set; }
public virtual Organization Organization { get; set; }
public virtual string Details { get; set; }
}
public class PeopleMap : ClassMap<People>
{
public PeopleMap()
{
Id(p => p.Id);
Map(p => p.FirstName).Length(100);
HasMany(x => x.LinkToOrganization)
.Table("PeopleOrganization")
.KeyColumn("PeopleId")
.AsBag()
.Inverse();
}
}
public class OrganizationMap : ClassMap<Organization>
{
public OrganizationMap()
{
Id(p => p.Id);
Map(p => p.Name).Length(50);
HasMany(x => x.LinkToPeople)
.Table("PeopleOrganization")
.KeyColumn("OrganizationId")
.AsBag()
.Inverse();
}
}
public class PeopleOrganizationMap : ClassMap<PeopleOrganization>
{
public PeopleOrganizationMap()
{
CompositeId()
.KeyReference(p => p.People, "PeopleId")
.KeyReference(p => p.Organization, "OrganizationId");
Map(p => p.Details).Length(100);
}
}
I now have to write the following code:
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // ITransaction.Begin() ISession.Save(people)
organizationRepository.Add(org); // ISession.Save(org)
peopleOrganizationRepository.Add(po); // ISession.Save(po)
unitOfWork.Commit(); // ITransaction.Commit()
My questions are:
Are my mappings correctly setup to support this kind of many-to-many scenario?
Is there are way for me to just be able to do the following (which would write to all three tables):
-
People people = new People { FirstName = "firstname" };
Organization org = new Organization { Name = "org" };
PeopleOrganization po = new PeopleOrganization { People = people, Organization = org, Details = "details" };
peopleRepository.Add(people); // Just ONE ISession.Save(people)
unitOfWork.Commit(); // ITransaction.Commit()
Any input is highly appreciated.
Thanks
Firstly, you'll need to add your newly created PeopleOrganization to the collection on both entities (Organization and People). Then if you add a Cascade.All() to your HasMany chain, the saves should propagate (cascade) down to the PeopleOrganization and subsequently to Organization.
As a purely semantic suggestion, I'd recommend encapsulating the PeopleOrganization creation into a method on Person. Something like this:
class Person
{
public void AddOrganization(Organization org, string details)
{
var link = new PeopleOrganization { ... };
LinkToOrganization.Add(link)
org.LinkToPeople.Add(link);
}
}
That way you never have to deal with creating the intrim entity yourself.