I have a table student and a table classes and a third table studentclasses having studentid and classic as foreign keys. I want to write a query that allows me to display the following:
{
"code": "10001",
"Name": "Lola",
"classes": [
{
"id": 1,
"name": "class1"
},
{
"id": 2,
"name": "class2"
},
{
"id": 3,
"name": "class3"
}
]
},
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp3
{
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<StudentClasses> StudentClasses { get; set; }
}
class Classes
{
public int Id { get; set; }
public string Name { get; set; }
public List<StudentClasses> StudentClasses { get; set; }
}
class StudentClasses
{
public int ClassesId { get; set; }
public int StudentId { get; set; }
public Classes Classes { get; set; }
public Student Student { get; set; }
}
class Program
{
static void Main(string[] args)
{
var mathClass = new Classes()
{
Id = 1,
Name = "Math"
};
var englishClass = new Classes()
{
Id = 1,
Name = "English"
};
var nermine = new Student()
{
Id = 1,
Name = "Nermine"
};
var nermineMath = new StudentClasses()
{
Classes = mathClass,
Student = nermine
};
var nermineEnglish = new StudentClasses()
{
Classes = englishClass,
Student = nermine
};
var data = new List<StudentClasses>() { nermineEnglish, nermineMath };
var result = data.Select(x =>
new
{
Code = x.Student.Id,
Name = x.Student.Name,
Classes = new { Id = x.Classes.Id, Name = x.Classes.Name }
}).ToList();
result.ForEach(x => Console.WriteLine(x));
//returns
//{ Code = 1, Name = Nermine, Classes = { Id = 1, Name = English } }
//{ Code = 1, Name = Nermine, Classes = { Id = 1, Name = Math } }
}
}
}
[System.Web.Http.Route("api/data/StudentClasses")]
public IEnumerable<Student> GetStudentClasses() {
var db = new School_DBEntities();
var xx = db.Students.ToList().Select(q=>new Student()
{
Student_ID=q.Student_ID,
First_Name=q.First_Name,
Classes=q.StudentsClasses.ToList().Select(e=>new Class()
{
Class_code=e.Class,
Class_Name=db.Classes.FirstOrDefault(v=>v.Class_code==e.Class).Class_Name
}).ToList()
});
return xx;
}
Related
public class Order
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public bool MarkAsPresent { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderItemId { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public Order Order { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
}
Please someone can help? From ASP.NET Core API controller, I need to return the following JSON. Please note that in the OrderItems, it includes the Product.ProductName. Also, if the Order.MarkAsPresent is true, then the productName should be replaced as 'Gift' in the JSON data.
{
"order": {
"orderId": "123",
"orderDate": "01-May-2021",
"orderItems": [
{ "productName": "Pen", "quantity": "25", "retailPrice": "3.50" },
{ "productName": "Paper", "quantity": "500", "retailPrice": "5.50" },
]
}
}
The two answers given so far are far too cumbersome. There's a very common, standard way to do this:
var result = from o in context.Order
select new
{
o.OrderId,
o.OrderDate,
OrderItems =
from oi in o.OrderItems
select new
{
ProductName = o.MarkAsPresent
? "Gift"
: oi.Product.ProductName,
oi.Quantity,
RetailPrice = oi.Price
}
};
You didn't define RetailPrice, so I assume it's just OrderItem.Price. If you like, the two select new statement can be replaced by projections into named DTO classes like OrderDto and OrderItemDto, respectively.
You can do it using an OrderDto.
public class OrderDto
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public object OrderItems { get; set; }
public OrderDto(int orderId, DateTime orderDate)
{
OrderId = orderId;
OrderDate = orderDate;
}
}//Cls
//--------------------------------------------//
public class OrderItemDto
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal RetailPrice { get; set; }
public OrderItemDto(string giftName, int quantity, decimal retailPrice)
{
ProductName = giftName;
Quantity = quantity;
RetailPrice = retailPrice;
}
}//Cls
//--------------------------------------------//
public class GiftDto
{
public string GiftName { get; set; }
public int Quantity { get; set; }
public decimal RetailPrice { get; set; }
public GiftDto(string giftName, int quantity, decimal retailPrice)
{
GiftName = giftName;
Quantity = quantity;
RetailPrice = retailPrice;
}
}//Cls
Add this method to your Order class
public class Order
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public bool MarkAsPresent { get; set; }
public List<OrderItem> OrderItems { get; set; }
public OrderDto ToDto()
{
var dto = new OrderDto(OrderId, OrderDate);
if (MarkAsPresent)
dto.OrderItems = OrderItems.Select(oi => new GiftDto(oi.Product.ProductName, oi.Quantity, oi.Price));
else
dto.OrderItems = OrderItems.Select(oi => new OrderItemDto(oi.Product.ProductName, oi.Quantity, oi.Price));
return dto;
}
}//Cls
Then in your action return something like Ok(order.ToDto());
Examples:
[HttpGet]
[AllowAnonymous]
public IActionResult OrderGift()
{
var order = new Order()
{
OrderId = 1,
OrderDate = DateTime.Now,
MarkAsPresent = true
};
order.OrderItems = new List<OrderItem>();
for (int i = 0; i < 5; i++)
{
order.OrderItems.Add(
new OrderItem()
{
Order = order,
OrderId = order.OrderId,
OrderItemId = i + 10,
Price = (i + 1) * 10,
Product = new Product()
{
ProductId = i + 20,
ProductName = $"Name {i}"
},
ProductId = i + 20,
Quantity = i * 2 + 4
});
}//for
return Ok(order.ToDto());
}///OrderGift
[HttpGet]
[AllowAnonymous]
public IActionResult OrderRegular()
{
var order = new Order()
{
OrderId = 1,
OrderDate = DateTime.Now,
MarkAsPresent = false
};
order.OrderItems = new List<OrderItem>();
for (int i = 0; i < 5; i++)
{
order.OrderItems.Add(
new OrderItem()
{
Order = order,
OrderId = order.OrderId,
OrderItemId = i + 10,
Price = (i + 1) * 10,
Product = new Product()
{
ProductId = i + 20,
ProductName = $"Name {i}"
},
ProductId = i + 20,
Quantity = i * 2 + 4
});
}//for
return Ok(order.ToDto());
}//OrderRegular
You can also use the Linq's let clause:
var report =
from o in orders
let orderItems =
from oi in o.OrderItems
select new {
productName = o.MarkAsPresent ? "Gift" : oi.Product.ProductName,
quantity = oi.Quantity,
retailPrice = oi.Price
}
select new {
orderId = o.OrderId,
orderDate = o.OrderDate,
orderItems
};
Use join to join the two models so we can connect every properties, and group all the
data by
OrderId and OrderDate, then just select the property you want to show.
About the gift,you can use if else in select.
Following is my demo:
[HttpGet]
public JsonResult Get()
{
var model = (from e in _context.OrderItems.Include(x=>x.Order).ToList()
join y in _context.Products.ToList() on e.OrderItemId equals y.ProductId
group new { e, y } by new { e.OrderId, e.Order.OrderDate } into g
select new
{
order = new
{
orderId = g.Key.OrderId,
orderDate = g.Key.OrderDate,
orderItems = (
from t in g.ToList()
select new
{
productName = t.e.Order.MarkAsPresent == true ? "Gift" : t.y.ProductName,
quantity = t.e.Quantity,
retailPrice = t.e.Price
}
).ToList()
}
}).ToList();
return new JsonResult(model);
}
Result:
I'm using the example on https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/custom-serialization.html#_serializing_type_information to get $type information for the documents in elasticsearch.
However as mentioned on the page this only returns type information for the outer document:
the type information is serialized for the outer MyDocument instance, but not for each MySubDocument instance in the SubDocuments collection.
So my question now is if anyone knows how to also get type information for sub documents?
I've tried using the same JsonSerializerSettings as in their example separate from Elasticsearch (using LinqPad) and there I get type information also for sub documents:
void Main()
{
var temp = new ListBlock
{
Id = 1,
Title = "Titel",
Blocks = new List<BlockContent> {
new InnerBlock {
Id = 11,
MyProperty ="Inner Property"
},
new InnerBlock2 {
Id = 12,
MyProperty2 = "Inner property 2"
}
}
};
var serializeOptions = new Newtonsoft.Json.JsonSerializerSettings
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Simple,
Formatting = Newtonsoft.Json.Formatting.Indented
};
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(temp, serializeOptions);
serialized.Dump();
}
public class BlockContent
{
public int Id { get; set; }
}
public class ListBlock : BlockContent
{
public string Title { get; set; }
public List<BlockContent> Blocks { get; set; }
}
public class ListBlock2 : BlockContent
{
public string Title2 { get; set; }
public List<BlockContent> Blocks { get; set; }
}
public class InnerBlock : BlockContent
{
public string MyProperty { get; set; }
}
public class InnerBlock2 : BlockContent
{
public string MyProperty2 { get; set; }
}
This code results in the following json:
{
"$type": "UserQuery+ListBlock, LINQPadQuery",
"Title": "Titel",
"Blocks": {
"$type": "System.Collections.Generic.List`1[[UserQuery+BlockContent, LINQPadQuery]], System.Private.CoreLib",
"$values": [
{
"$type": "UserQuery+InnerBlock, LINQPadQuery",
"MyProperty": "Inner Property",
"Id": 11
},
{
"$type": "UserQuery+InnerBlock2, LINQPadQuery",
"MyProperty2": "Inner property 2",
"Id": 12
}
]
},
"Id": 1
}
Using these versions at the moment:
Elasticsearch 7.4.2
Nest 7.4.2
Update:
The solution provided by Russ Cam below works like a charm for the data model included in the response, however I've put together an example below based on how we create indices (using automap) and bulk index the initial list of documents. This works fine if we exclude the list of Guids (CategoryIds) in the model but if we include it the following exception is thrown:
{
"took": 8,
"errors": true,
"items": [{
"index": {
"_index": "testindex",
"_type": "_doc",
"_id": "1",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [categoryIds] of type [keyword] in document with id '1'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
"caused_by": {
"type": "illegal_state_exception",
"reason": "Can't get text on a START_OBJECT at 1:140"
}
}
}
}, {
"index": {
"_index": "testindex",
"_type": "_doc",
"_id": "2",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [categoryIds] of type [keyword] in document with id '2'. Preview of field's value: '{$values=[], $type=System.Collections.Generic.List`1[[System.Guid, System.Private.CoreLib]], System.Private.CoreLib}'",
"caused_by": {
"type": "illegal_state_exception",
"reason": "Can't get text on a START_OBJECT at 1:141"
}
}
}
}
]
}
Here is a simple (.Net 5) console application where this behaviour hopefully can be reproduced by others also:
using System;
using System.Collections.Generic;
using System.Linq;
using Elasticsearch.Net;
using Nest;
using Nest.JsonNetSerializer;
using Newtonsoft.Json;
namespace ElasticsearchTypeSerializer
{
internal class Program
{
private const string IndexName = "testindex";
private static void Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool,
(builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
settings.DisableDirectStreaming();
var client = new ElasticClient(settings);
CreateIndex(client);
IndexDocuments(client);
var documents = GetDocuments(client);
}
private static void CreateIndex(IElasticClient client)
{
var createIndexResponse = client.Indices.Create(IndexName, x => x.Map<MyDocument>(m => m.AutoMap()));
}
private static void IndexDocuments(IElasticClient client)
{
var documents = new List<MyDocument>
{
new()
{
Id = 1,
Name = "My first document",
OwnerId = 2,
SubDocuments = new List<SubDocument>
{
new MySubDocument {Id = 11, Name = "my first sub document"},
new MySubDocument2 {Id = 12, Description = "my second sub document"}
}
},
new()
{
Id = 2,
Name = "My second document",
OwnerId = 3,
SubDocuments = new List<SubDocument>
{
new MySubDocument {Id = 21, Name = "My third sub document"}
}
}
};
var bulkIndexResponse = client.Bulk(b => b.Index(IndexName).IndexMany(documents).Refresh(Refresh.True));
}
private static IEnumerable<MyDocument> GetDocuments(IElasticClient client)
{
var searchResponse = client.Search<MyDocument>(s => s.Index(IndexName).Query(q => q.MatchAll()));
var documents = searchResponse.Documents.ToList();
return documents;
}
}
public class MyDocument
{
public int Id { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public int OwnerId { get; set; }
public List<Guid> CategoryIds { get; set; } = new();
public List<SubDocument> SubDocuments { get; set; }
}
public class SubDocument
{
public int Id { get; set; }
}
public class MySubDocument : SubDocument
{
public string Name { get; set; }
}
public class MySubDocument2 : SubDocument
{
public string Description { get; set; }
}
public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
{
public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer,
IConnectionSettingsValues connectionSettings)
: base(builtinSerializer, connectionSettings)
{
}
protected override JsonSerializerSettings CreateJsonSerializerSettings()
{
return new()
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
}
}
}
Any help regarding this issue is very much appreciated!
If you want type information to be included for typed collections on a document, the derived contract resolver can be omitted, which supresses the type handling for collection item types
private static void Main()
{
var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200"));
var settings = new ConnectionSettings(pool,
(builtin, settings) => new MySecondCustomJsonNetSerializer(builtin, settings));
var client = new ElasticClient(settings);
var document = new MyDocument
{
Id = 1,
Name = "My first document",
OwnerId = 2,
SubDocuments = new[]
{
new MySubDocument { Name = "my first sub document" },
new MySubDocument { Name = "my second sub document" },
}
};
var indexResponse = client.IndexDocument(document);
}
public class MyDocument
{
public int Id { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public int OwnerId { get; set; }
public IEnumerable<MySubDocument> SubDocuments { get; set; }
}
public class MySubDocument
{
public string Name { get; set; }
}
public class MySecondCustomJsonNetSerializer : ConnectionSettingsAwareSerializerBase
{
public MySecondCustomJsonNetSerializer(IElasticsearchSerializer builtinSerializer, IConnectionSettingsValues connectionSettings)
: base(builtinSerializer, connectionSettings) { }
protected override JsonSerializerSettings CreateJsonSerializerSettings() =>
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
NullValueHandling = NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};
}
I use nest 7.0.0 and asp.net core 2.2. i want to create indxe from poco class. in elasticsearch index create but without any mapping. create index method is :
public async Task CreateIndex()
{
try
{
var getIndexResponse =await ElasticClient.Indices.GetAsync("myindex");
if (getIndexResponse.Indices == null || !getIndexResponse.Indices.Any())
{
var createIndexResponse = await ElasticClient.Indices.CreateAsync("myindex", c => c
.Map(p => p.AutoMap<MyModel>()));
}
}
catch (Exception)
{
}
}
and MyModel is like this:
[ElasticsearchType(IdProperty = nameof(Id), RelationName = "MyModelMessage")]
public class MyModel
{
[Number(NumberType.Long, Index = true, DocValues = false)]
public long UserId { get; set; }
[Date(Index = true)]
public DateTime CreatedAt { get; set; }
[Text(Index = false)]
public string ObjectName { get; set; }
[Date(Index = true)]
public DateTime UpdateAt { get; set; }
}
I checked your code against NEST 7.0.1 and elasticsearch 7.2.0 and mapping was created for the type:
class Program
{
[ElasticsearchType(IdProperty = nameof(Id), RelationName = "MyModelMessage")]
public class MyModel
{
[Number(NumberType.Long, Index = true, DocValues = false)]
public long UserId { get; set; }
[Date(Index = true)] public DateTime CreatedAt { get; set; }
[Text(Index = false)] public string ObjectName { get; set; }
[Date(Index = true)] public DateTime UpdateAt { get; set; }
}
static async Task Main(string[] args)
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(pool);
connectionSettings.DefaultIndex("documents");
var client = new ElasticClient(connectionSettings);
var getIndexResponse = await client.Indices.GetAsync("myindex");
if (getIndexResponse.Indices == null || !getIndexResponse.Indices.Any())
{
var createIndexResponse = await client.Indices.CreateAsync("myindex", c => c
.Map(p => p.AutoMap<MyModel>()));
Console.WriteLine("index created");
}
}
}
and http://localhost:9200/myindex/_mapping returns:
{
"myindex": {
"mappings": {
"properties": {
"createdAt": {
"type": "date"
},
"objectName": {
"type": "text",
"index": false
},
"updateAt": {
"type": "date"
},
"userId": {
"type": "long",
"doc_values": false
}
}
}
}
}
Is something missing?
Is it possible in odata4 to create a model such as:
public class PuppyDogs
{
public string Name { get; set; }
public virtual IList<Bone> Bones { get; set; }
}
public class Bone
{
public string ChewType { get; set; }
public int Numberofchews { get; set; }
}
And the controller class looks like
public class PuppyDogController : ODataController
{
List<PuppysDog> mydogs = new List<PuppysDog>();
private PuppyDogController()
{
if (mydogs.Count == 0)
{
PuppysDog mydog = new PuppysDog();
mydog.Name = "Fido";
mydog.Bones = new List<Bone>()
{
new Bone{ ChewType = "Soft", Numberofchews=1 },
new Bone{ ChewType = "Hard", Numberofchews=2 }
};
mydogs.Add(mydog);
}
}
[EnableQuery]
public IQueryable<PuppysDog> Get()
{
return mydogs.AsQueryable();
}
}
Can I include the Bones property of PuppyDogs without using expand? By default Bones is not returned to the client.
There are several things nor clear in your code, for example, the entity set PuppyDogs don't have a key, the naming convention in the controller is a little wired and etc. With the following code, it can work perfectly, please take a look
PuppyDog.cs
public class PuppyDog
{
[Key]
public string Name { get; set; }
public virtual IList<Bone> Bones { get; set; }
}
Bone.cs
public class Bone
{
public string ChewType { get; set; }
public int Numberofchews { get; set; }
}
PupyyDogsController.cs
public class PuppyDogsController : ODataController
{
List<PuppyDog> mydogs = new List<PuppyDog>();
private PuppyDogsController()
{
if (mydogs.Count == 0)
{
PuppyDog mydog = new PuppyDog();
mydog.Name = "Fido";
mydog.Bones = new List<Bone>()
{
new Bone {ChewType = "Soft", Numberofchews = 1},
new Bone {ChewType = "Hard", Numberofchews = 2}
};
mydogs.Add(mydog);
}
}
[EnableQuery]
public IQueryable<PuppyDog> Get()
{
return mydogs.AsQueryable();
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<PuppyDog>("PuppyDogs");
config.MapODataServiceRoute("odata", null, builder.GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
}
Then when try http://localhost:21830/PuppyDogs, I can successfully got the payload as
{
"#odata.context": "http://localhost:21830/$metadata#PuppyDogs",
"value": [
{
"Name": "Fido",
"Bones": [
{
"ChewType": "Soft",
"Numberofchews": 1
},
{
"ChewType": "Hard",
"Numberofchews": 2
}
]
}
]
}
I am trying to use Linq Union to add additional record into result but Union do not work. Maybe someone could point me in right direction.
public class ProductView
{
public int Id { get; set; }
public bool Active { get; set; }
public string Name { get; set; }
public int ProductTypeId { get; set; }
public int UserCount { get; set; }
}
void Main()
{
var product = Products.Select(p => new ProductView
{
Id = p.Id,
Active = p.Active,
Name = p.Name,
ProductTypeId = p.ProductTypeId,
UserCount = 1
}).ToList();
//The new item is not jointed to the result above
product.Union(new[] {
new ProductView
{
Id = 9999,
Active = true,
Name = "Test",
ProductTypeId=0,
}
});
product.Dump();
}
You need to store the output:
var product2 = product.Union(new[] {
new ProductView
{
Id = 9999,
Active = true,
Name = "Test",
ProductTypeId=0,
}
});
product2.Dump();
In addition to this, overriding the Equals behaviour would be useful - as you probably want to check equality using just the Id field?
For example, if you don't override the Equals behaviour, then you will get Object reference equals like this:
void Main()
{
var list = new List<Foo>()
{
new Foo() { Id = 1},
new Foo() { Id = 2},
new Foo() { Id = 3},
};
var list2 = new List<Foo>()
{
new Foo() { Id = 1},
new Foo() { Id = 2},
new Foo() { Id = 3},
};
var query = list.Union(list2);
query.Dump();
}
// Define other methods and classes here
public class Foo
{
public int Id {get;set;}
}
produces six items!
But if you change Foo to:
public class Foo
{
public int Id {get;set;}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Foo)) return false;
var foo= (Foo)obj;
return this.Id == foo.Id;
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
}
then you will get 3 items - which is probably what you are expecting.
You need to override Equals and GetHashCode in ProductView if you want to use Union in a meaningful way(other than comparing by refence).
public class ProductView
{
public int Id { get; set; }
public bool Active { get; set; }
public string Name { get; set; }
public int ProductTypeId { get; set; }
public int UserCount { get; set; }
public override bool Equals(object obj)
{
if (obj == null || !(obj is ProductView)) return false;
ProductView pv2 = (ProductView)obj;
return this.Id == pv2.Id;
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
}
You could also implement an IEqualityComparer<ProductView> in a similar way and use it for this overload of Union.