DocumentDB LINQ Query - Select method not supported - join

The Application:
.Net Standard Class Library (Containing a series of Repositories)
Latest version https://www.nuget.org/packages/Microsoft.Azure.DocumentDB.Core
Azure Cosmos DB Emulator
Sample Document Structure:
{
"ProfileName": "User Profile 123",
"Country": "UK",
"Tags": [{
"Id": "686e4c9c-f1ab-40ce-8472-cc5d63597263",
"Name": "Tag 1"
},
{
"Id": "caa2c2a0-cc5b-42e3-9943-dcda776bdc20",
"Name": "Tag 2"
}],
"Topics": [{
"Id": "baa2c2a0-cc5b-42e3-9943-dcda776bdc20",
"Name": "Topic A"
},
{
"Id": "aaa2c2a0-cc5b-42e3-9943-dcda776bdc30",
"Name": "Topic B"
},
{
"Id": "eaa2c2a0-cc5b-42e3-9943-dcda776bdc40",
"Name": "Topic C"
}]
}
The Problem:
The issue I have is that any LINQ query I execute that contains a .Select, returns an error stating that the Select method is not supported.
What I Need?
I want to be able to use a LINQ Expression to return all documents WHERE:
Country = UK
Tags contains a specific GUID
Topics contain a specific GUID

What I Need? I want to be able to use a LINQ Expression to return all documents
In your case, the Tags and Topic are object array, if you use the following linq code it will get null.
var q = from d in client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("database", "coll"))
where d.Country == "UK" && d.Tags.Contains(new Tag
{
Id = "686e4c9c-f1ab-40ce-8472-cc5d63597264"
}) && d.Topics.Contains(new Topic
{
Id = "baa2c2a0-cc5b-42e3-9943-dcda776bdc22"
})
select d;
I also try to override IEqualityComparer for Tag and Topic
public class CompareTag: IEqualityComparer<Tag>
{
public bool Equals(Tag x, Tag y)
{
return x != null && x.Id.Equals(y?.Id);
}
public int GetHashCode(Tag obj)
{
throw new NotImplementedException();
}
}
And try it again then get Contains is not supported by Azure documentDb SDK
var q = from d in client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("database", "coll"))
where d.Country == "UK" && d.Tags.Contains(new Tag
{
Id = "686e4c9c-f1ab-40ce-8472-cc5d63597264"
},new CompareTag()) && d.Topics.Contains(new Topic
{
Id = "baa2c2a0-cc5b-42e3-9943-dcda776bdc22"
}, new CompareTopic())
select d;
My workaround is that we could use the SQL query directly. It works correctly on my side. We also could test it on the Azure portal.
SELECT * FROM root WHERE (ARRAY_CONTAINS(root.Tags, {"Id": "686e4c9c-f1ab-40ce-8472-cc5d63597263"}, true) And ARRAY_CONTAINS(root.Topics, {"Id": "baa2c2a0-cc5b-42e3-9943-dcda776bdc20"}, true) And root.Country = 'UK' )
Query with SDK
FeedOptions queryOptions = new FeedOptions { MaxItemCount = -1 };
var endpointUrl = "https://cosmosaccountName.documents.azure.com:443/";
var primaryKey = "VNMIT4ydeC.....";
var client = new DocumentClient(new Uri(endpointUrl), primaryKey);
var sql = "SELECT * FROM root WHERE (ARRAY_CONTAINS(root.Tags, {\"Id\":\"686e4c9c-f1ab-40ce-8472-cc5d63597263\"},true) AND ARRAY_CONTAINS(root.Topics, {\"Id\":\"baa2c2a0-cc5b-42e3-9943-dcda776bdc20\"},true) AND root.Country = \"UK\")";
var profileQuery = client.CreateDocumentQuery<Profile>(
UriFactory.CreateDocumentCollectionUri("dbname", "collectionName"),sql, queryOptions).AsDocumentQuery();
var profileList = new List<Profile>();
while (profileQuery.HasMoreResults)
{
profileList.AddRange(profileQuery.ExecuteNextAsync<Profile>().Result);
}
Profile.cs file
public class Profile
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public string Country { get; set; }
public string ProfileName { get; set; }
public Tag[] Tags{ get; set; }
public Topic[] Topics { get; set; }
}
Topic.cs
public class Topic
{
public string Id { get; set; }
public string Name { get; set; }
}
Tag.cs
public class Tag
{
public string Id { get; set; }
public string Name { get; set; }
}

Related

How to stop .net core 6 webapp with Odata query from inlining dictionary?

I have a rather simple(for now) Odata controller that does not work in the way I expect.
Backend store is a Mongo database, but I don't think that is relevant.
Model class:
public class EventModel
{
[Key]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string EventName { get; set; }
public DateTime EventTime { get; set; }
// Fields omitted to shorten sample
public IDictionary<string,object?> Data { get; set; }
}
OData controller is added like so:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Repository.Model.EventModel>("Events");
services.AddControllers()
.AddOData(options => options
.Select()
.Filter()
.OrderBy()
.Expand()
.Count()
.SetMaxTop(null)
.AddRouteComponents("eventservice/odata", builder.GetEdmModel()));
And this is the controller:
public class EventODataController : ODataController
{
private readonly IEventRepository _repository;
public EventODataController(IEventRepository repository)
{
_repository = repository;
}
[HttpGet("eventservice/odata/Events")]
[EnableQuery]
public ActionResult Get()
{
var data = _repository.GetAsQueryable();
return Ok(data);
}
Expected result when doing a HTTP get to eventservice/odata/Events$top=100 should be along the lines of this:
{
"#odata.context": "http://127.0.0.1:7125/eventservice/odata/$metadata#Events",
"#odata.count": 1080302,
"value": [
{
"Id": "63dbbcc9920829279f559025",
"EventName": "Asset",
"EventTime": "2022-09-27T09:14:15.398+02:00",
// Omitted field data here
"Data": {
"Data1":"Foo",
"Data2":"Bar",
"SomeMore":4
}
}
But it turns out that OData somehow flattens/inlines the dictionary, so the result is this:
"#odata.context": "http://127.0.0.1:7125/eventservice/odata/$metadata#Events",
"#odata.count": 1080302,
"value": [
{
"Id": "63dbbcc9920829279f559025",
"EventName": "Asset",
"EventTime": "2022-09-27T09:14:15.398+02:00",
"AssetId#odata.type": "#Int64",
"AssetId": 1258,
"UniqueCode": "9404120007",
"DbId#odata.type": "#Int64",
"DbId": 1038118,
"SomeData": "ABC",
"MoreData": "108",
"AreaName": "Area51,
...
},
Simple question: How do I stop OData from behaving like this, and do what I expect?

MS Graph Create open extension call with Json object

I am trying to create events using MS Graph. We are using json objects with the call as in this example
using (HttpClient httpClient = new HttpClient())
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", msBearerToken);
var callJson = new
{
Subject = EventSummery.Title,
Body = new
{
ContentType = BodyType.Html.ToString(),
Content = EventSummery.Description
},
Start = new
{
DateTime = EventSummery.StartDateUTC.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "GMT Standard Time"
},
End = new
{
DateTime = EventSummery.EndDateUTC.ToString("yyyy-MM-ddTHH:mm:ss"),
TimeZone = "GMT Standard Time"
}
};
I need to add an open extension however the documentation I need to add this attribute
"extensions": [
{
"#odata.type": "microsoft.graph.openTypeExtension",
"extensionName": "Com.Contoso.Referral",
"companyName": "Wingtip Toys",
"expirationDate": "2015-12-30T11:00:00.000Z",
"dealValue": 10000
}]
however #odata.type throws an error if I put it in this form:
Extensions = new
{
"#odata.type": "microsoft.graph.openTypeExtension",
"extensionName": "Com.Contoso.Referral",
"companyName": "Wingtip Toys",
"expirationDate": "2015-12-30T11:00:00.000Z",
"dealValue": 10000
}
What am I missing how can I make this call successfully?
I'm afraid that you cannot create anonymous object with property that has a special character at the beginning.
Only way is to create a class with properties that have JsonPropertyName attribute and then use json serializer.
public class OpenTypeExtension
{
[JsonPropertyName("#odata.type")]
public string ODataType { get; set; }
[JsonPropertyName("extensionName")]
public string ExtensionName { get; set; }
[JsonPropertyName("companyName")]
public string CompanyName { get; set; }
[JsonPropertyName("expirationDate")]
public string ExpirationDate{ get; set; }
[JsonPropertyName("dealValue")]
public string DealValue{ get; set; }
public OpenTypeExtension()
{
ODataType = "microsoft.graph.openTypeExtension";
}
}

Error Trying to pass a IEnumerable list with Linq and Mvc Web API

I'm trying to return a result from a list with a Linq method but i get an error saying that I "implictly can't convert my class to generic list. I'm using Get... method.
Would very much appreciate if someone could help me out with this.
This is my Tamagotchi class:
public class Tamagotchi
{
public string Name { get; set; }
public DateTime Born { get; set; }
public int Health { get; set; }
}
This is the API:
public class SomeController : ApiController
{
List<Tamagotchi> Gotchis = new List<Tamagotchi>
{
new Tamagotchi { Born = DateTime.Now.AddYears(-5), Health = 30, Name = "XTP" },
new Tamagotchi { Born = DateTime.Now.AddYears(-4), Health = 49, Name = "ZQX" },
new Tamagotchi { Born = DateTime.Now.AddYears(-3), Health = 15, Name = "VBR" },
new Tamagotchi { Born = DateTime.Now.AddYears(-2), Health = 87, Name = "BNQP" },
new Tamagotchi { Born = DateTime.Now.AddYears(-1), Health = 62, Name = "VLW" },
};
public IEnumerable<Tamagotchi> Get2()
{
var _result = Gotchis.SingleOrDefault(tama => tama.Name == "VLW");
return _result;
}
}
Thank you!
/Chris
SingleOrDefault will return a single Tamagutchi, so the result is not an IEnumerable at all. Perhaps you mean
public Tamagotchi Get2()
{
var _result = Gotchis.SingleOrDefault(tama => tama.Name == "VLW");
return _result;
}
or
public IEnumerable<Tamagotchi> Get2()
{
var _result = Gotchis.Where(tama => tama.Name == "VLW");
return _result;
}

A circular reference was detected while serializing an object of type?

DB MetersTree TABLE
id text parentId state
0 root 0 open
1 level 1 1 open
2 level 1 1 open
...
CONTROLLER
public ActionResult GetDemoTree()
{
OsosPlus2DbEntities entity = new OsosPlus2DbEntities();
MetersTree meterTree = entity.MetersTree.FirstOrDefault();
return Json(meterTree, JsonRequestBehavior.AllowGet);
}
DATA FORMAT THAT SHOULD BE (for example)
[{
"id": 1,
"text": "Node 1",
"state": "closed",
"children": [{
"id": 11,
"text": "Node 11"
},{
"id": 12,
"text": "Node 12"
}]
},{
"id": 2,
"text": "Node 2",
"state": "closed"
}]
How can I create tree Json Data? If I write MetersTree with its relationships I get the error that is defined in the title.
You need to break the circular reference that is being picked up because of the navigational property in your EF class.
You can map the results into an anonymous type like this, although this is untested:
public ActionResult GetDemoTree()
{
OsosPlus2DbEntities entity = new OsosPlus2DbEntities();
MetersTree meterTree = entity.MetersTree.FirstOrDefault();
var result = from x in meterTree
select new
{
x.id,
x.text,
x.state,
children = x.children.Select({
c => new {
c.id,
c.text
})
};
return Json(result, JsonRequestBehavior.AllowGet);
}
I solved it like this:
VIEW MODEL
public class MetersTreeViewModel
{
public int id { get; set; }
public string text { get; set; }
public string state { get; set; }
public bool #checked { get; set; }
public string attributes { get; set; }
public List<MetersTreeViewModel> children { get; set; }
}
CONTROLLER
public ActionResult GetMetersTree()
{
MetersTree meterTreeFromDb = entity.MetersTree.SingleOrDefault(x => x.sno == 5); //in my db this is the root.
List<MetersTreeViewModel> metersTreeToView = buildTree(meterTreeFromDb.Children).ToList();
return Json(metersTreeToView, JsonRequestBehavior.AllowGet);
}
BuildTree Method
private List<MetersTreeViewModel> BuildTree(IEnumerable<MetersTree> treeFromDb)
{
List<MetersTreeViewModel> metersTreeNodes = new List<MetersTreeViewModel>();
foreach (var node in treeFromDb)
{
if (node.Children.Any())
{
metersTreeNodes.Add(new MetersTreeViewModel
{
id = node.sno,
text = node.Text,
state = node.Text,
children = BuildTree(node.Children)
});
}
else {
metersTreeNodes.Add(new MetersTreeViewModel
{
id = node.sno,
text = node.Text,
state = node.Text
});
}
}
return metersTreeNodes;
}
Thanks to all who are interested in ...

How to convert a list of anonymous object to string array using linq for return using Json Result in ASP.NET MVC

I currently have a list of a book object as follows:
public class Book()
{
public int BookId { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
List<Book> books = BookRepository.SelectAll();
I would like to return a string list/array of Authors for return via a Json Result in my action method. At the moment I have done:
var result = books.Select(p => new { p.Author }).ToList();
return Json(new { authors = result });
However, inspecting the result gives the following JSON:
{
authors: [
{ Author: "John" },
{ Author: "Gary" },
{ Author: "Bill" },
{ Author: "Ray" }
]
}
However, I do not want each Author as a seperate object in the JSON. I would like the result as:
{
authors: ["John", "Gary", "Bill", "Ray"]
}
How do I go about achieving this?
have you tried:
// this will return a List<string>
var result = books.Select(p => p.Author).ToList();
return Json(new { authors = result });

Resources