Problem querying a DynamoDB GSI Composite key using .NET Core Object Persistence Model - asp.net

I want to query a GSI composite key using OPM. I followed the documentation example the link for which is as follows:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.QueryScan.html
However I am facing two issues:
1). The same code as mentioned in the above example is throwing errors for me. I believe inside the context.QueryAsync(Hask Key target value, Operator.Between, RangeKey lower target value, RangeKey higher target value) is the syntax.
But I get error as follows for the last two range key target values:
Error CS1503 Argument 3: cannot convert from 'string' to 'System.Collections.Generic.IEnumerable'
2). How do I set the query to target a GSI instead of Normal Composite Keys.
For instructing the query to target GSI I googled and found the below code snippet : DynamoDBOperationConfig(). But how do I incorporate it into the final Context.QueryAsync method?
using Amazon.DynamoDBv2.DataModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace EDCPA.Core.Models
{
[DynamoDBTable("EDCBUILDDATA1")]
public class AARes
{
[DynamoDBGlobalSecondaryIndexHashKey("PN-FGD-Index")]
public string ProjectName { get; set; }
[DynamoDBGlobalSecondaryIndexRangeKey("PN-FGD-Index")]
public string FileGeneratedDate { get; set; }
public string VehicleName { get; set; }
public string FileNameKey { get; set; }
[DynamoDBHashKey]
public string Id { get; set; }
[DynamoDBRangeKey]
public string createdTimeStamp { get; set; }
}
}
using Amazon.DynamoDBv2.DataModel;
using System;
using System.Collections.Generic;
using System.Text;
namespace EDCPA.Core.Models
{
[DynamoDBTable("EDCBUILDDATA1")]
public class AAReq
{
public string FileGeneratedFromDate { get; set; }
public string FileGeneratedToDate { get; set; }
public string ProjectName { get; set; }
public string VehicleName { get; set; }
[DynamoDBHashKey]
public string Id { get; set; }
[DynamoDBRangeKey]
public string createdTimeStamp { get; set; }
}
}
public async Task<IActionResult> AARequestAsync([FromBody] AAReq req)
{
try
{
headers = HeaderCollections.TryRetrieveToken(Request);
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new StoredProfileAWSCredentials(),
RegionEndpoint.APSouth1);
DynamoDBContext context = new DynamoDBContext(client);
DynamoDBOperationConfig indexHashRangeOpConfig = new DynamoDBOperationConfig()
{
IndexName = "PN-FGD-Index",
ConsistentRead = false,
};
IEnumerable<AARes> fileKeys = await
context.QueryAsync<AARes>(req.ProjectName, QueryOperator.Between, req.FileGeneratedFromDate, req.FileGeneratedToDate);
Console.WriteLine("\nFindRepliesInLast15Days: Printing result.....");
foreach (AARes r in fileKeys)
Console.WriteLine("{0}\t{1}\t{2}\t{3}", r.FileNameKey, r.VehicleName);
1). What am I doing wrong inside the context.QueryAsync statement?
2). Also,How do I incorporate the DynamoDBOperationConfig indexHashRangeOpConfig into my final query statement?

Here is the solution to my problems above:
List<Dashboardreq> list =
await context.QueryAsync<Dashboardreq>(req.ProjectName, QueryOperator.Between, new string[] {
req.FileGeneratedFromDate+" " + Constants.DayBeginTime,
req.FileGeneratedToDate+" " + Constants.DayEndTime
}, indexHashRangeOpConfig).GetRemainingAsync();

Related

Passing Date in an API

I am working on .Net Core and want to post attributes in the api containing dates but I m not able to handle dates for an API. How to overcome with this error.
using System;
using System.Collections.Generic;
namespace Server.Dtos
{
public class ProjectSessionDto
{
public int Id { get; set; }
public string Activity { get; set; }
public string ResourcePerson { get; set; }
public DateTime? TentativeDate { get; set; }
public DateTime SubmissionDate { get; set; }
public int SemesterId { get; set; }
public string Program { get; set; }
public string Batch { get; set; }
}
}
Controller Code...
[HttpPost("addprojectsession")] //Since there would be 2 Post Methods. Login and Register
public async Task<IActionResult> AddProjectSession(ProjectSessionDto projectsession)
{
var semester = await _semester.GetSemesterWithProjectSession(projectsession.SemesterId);
semester.projectsessions.Add(_mapper.Map<ProjectSessionDto,ProjectSession>(projectsession));
await _semester.AddSemesterWithProject(semester);
return Ok();
}
}
There are two issues you need to fix :
In Postman ,change the values with double quotes:
"tentativeDate": "12/12/2019",
"SubmissionDate":"12/12/2019"
In controller , use [FromBody]to make asp.net core model binding wokring for reading value from request body :
public async Task<IActionResult> AddProjectSession([FromBody]ProjectSessionDto projectsession)

How to create a list page for Users in .Net Core 2

We extended the Identity Roles as well Users:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ApplicationRole : IdentityRole
{
public ApplicationRole() : base() { }
public ApplicationRole(string roleName) : base(roleName) { }
public ApplicationRole(string roleName, string description, DateTime createdDate) : base(roleName)
{
base.Name = roleName;
this.Description = description;
this.CreatedDate = createdDate;
}
public string Description { get; set; }
public DateTime CreatedDate { get; set; }
}
}
and
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ApplicationUser : IdentityUser
{
public ApplicationUser() : base() { }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Suburb { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public string Country { get; set; }
[InverseProperty("ApplicationUser")]
public IList<HostApplicationUser> HostApplicationUsers { get; set; }
[InverseProperty("HostApplicationCreatedUser")]
public HostApplicationUser HostApplicationCreatedUser { get; set; }
[InverseProperty("HostApplicationLastModifiedUser")]
public HostApplicationUser HostApplicationLastModifiedUser { get; set; }
[InverseProperty("ApplicationUser")]
public IList<ClubApplicationUser> ClubApplicationUsers { get; set; }
[InverseProperty("ClubApplicationCreatedUser")]
public ClubApplicationUser ClubApplicationCreatedUser { get; set; }
[InverseProperty("ClubApplicationLastModifiedUser")]
public ClubApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
We are trying to create a Razor Page list of users as well their role:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
public class IndexModel : PageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
public IndexModel(AthlosifyWebArchery.Data.ApplicationDbContext context)
{
_context = context;
}
public List<ApplicationUser> User { get; private set; }
public List<IdentityUserRole<string>> UsersRoles { get; set; } // get my roles or context of user
public List<IdentityRole> AllRoles { get; private set; }
public async Task OnGetAsync()
{
User = _context.Users.Include("UserRoles").ToList();
//UsersRoles = _context.UserRoles.ToList(); // get my roles or context of user
//AllRoles = _context.Roles.ToList();
}
}
}
We managed to get just the user list BUT not sure on how to include the Roles in this case.
Any pointer please?
Firstly, try to avoid using Include function's string overload. Using the lamda instead will help you be sure that the property exists. For instance, in this case, a property named UserRoles doesn't exist for your user class in the first place. Secondly, the syntax you are trying to use it generally used for a one to many relationships. Note that users and roles is a many to many relation and the identity context (that your dbcontext extended) has a UserRoles property for this. You should be able to get all users joined with their roles using a query like this:
IEnumerable<User> users = from u in context.Users
from r in context.Roles
from ur in context.UserRoles
where u.Id == ur.UserId && ur.RoleId == r.Id
select u;

ServiceStack OrmLite CustomSelect not working?

I'm trying to use the feature documented here :
https://github.com/ServiceStack/ServiceStack.OrmLite#custom-sql-customizations
This is how I'm using it:
var q = Db.From<MemberAccess>().LeftJoin<Member>();
return Db.Select<MemberResponse>(q);
Response object:
public class MemberResponse
{
public Guid Id { get; set; }
public string MemberFirstName { get; set; }
public string MemberLastName { get; set; }
public string MemberEmail { get; set; }
[Default(OrmLiteVariables.SystemUtc)]
public string AccessedOn { get; set; }
[CustomSelect("CONCAT(LEFT(Member.FirstName, 1),LEFT(Member.LastName,1))")]
public string MemberInitial { get; set; }
}
It seems like whatever I put in CustomSelect doesn't get used. Maybe, I'm not using this correctly? Also, the Default attribute doesn't work either.I tried that as it was an example from the doco.
Any idea will be appreciated.
Thanks in advance.
The [CustomSelect] only applies to the source table. Selecting the results in a custom type is used to map the returned resultset on the MemberResponse type, it doesn't have any effect on the query that gets executed.
Likewise with [Default(OrmLiteVariables.SystemUtc)] that's used to define the default value when creating the table which is only used when it creates the Column definition, so it's only useful on the source Table Type.
Both these attributes should only be added on the source MemberAccess to have any effect, which your mapped MemberResponse can access without any attributes, e.g:
public class MemberResponse
{
public Guid Id { get; set; }
public string MemberFirstName { get; set; }
public string MemberLastName { get; set; }
public string MemberEmail { get; set; }
public string AccessedOn { get; set; }
public string MemberInitial { get; set; }
}
Sql.Custom() API
The new Sql.Custom() API added in v4.5.5 that's available on MyGet will let you select a custom SQL Fragment, e.g:
var q = Db.From<MemberAccess>().LeftJoin<Member>()
.Select<MemberAccess,Member>((a,m) => new {
Id = a.Id,
MemberFirstName = m.FirstName,
MemberLastName = m.LastName,
MemberEmail = m.Email,
MemberInitial = Sql.Custom("CONCAT(LEFT(Member.FirstName,1),LEFT(Member.LastName,1))")
});
return Db.Select<MemberResponse>(q);

Using DTO's with OData & Web API

Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.
I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():
public class SalesOrdersController : ODataController
{
private DbContext _DbContext;
public SalesOrdersController(DbContext context)
{
_DbContext = context;
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
}
}
AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
.ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());
ExplicitExpansion() then creates a new problem where the following request throws an error:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities
The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?
The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:
[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}
public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
if (source == null) { return new string[]{}; }
var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');
for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
{
// Need to transform the odata syntax for expanding properties to something EF will understand:
// OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";
// But EF wants it like this: "SalesOrderLines.MasterStockRecord";
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
}
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');
//Now do the same for Select (incomplete)
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();
return propertiesToExpand;
}
This works for expand, so now I can handle a request like the following:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines
or a more complicated request like:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)
However, more complicated request that try to combine $select with $expand will fail:
/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)
Sequence contains no elements
So, the question is: am I approaching this the right way?
It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.
It seems this is a rather popular topic:
odata-expand-dtos-and-entity-framework
how-to-specify-the-shape-of-results-with-webapi2-odata-with-expand
web-api-queryable-how-to-apply-automapper
how-do-i-map-an-odata-query-against-a-dto-to-another-entity
While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.
Classes and Config below:
Entity Framework (V6.1.3) entities:
public class SalesOrderHeader
{
public string SalesOrderNumber { get; set; }
public string Alpha { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}
public class SalesOrderLine
{
public string SalesOrderNumber { get; set; }
public string OrderLineNumber { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderHeader SalesOrderHeader { get; set; }
public virtual MasterStockRecord MasterStockRecord { get; set; }
}
public class MasterStockRecord
{
public string ProductCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData (V6.13.0) Data Transfer Objects:
public class SalesOrderDto
{
[Key]
public string SalesOrderNumber { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}
public class SalesOrderLineDto
{
[Key]
[ForeignKey("SalesOrderHeader")]
public string SalesOrderNumber { get; set; }
[Key]
public string OrderLineNumber { get; set; }
public string LineType { get; set; }
public string Product { get; set; }
public string Description { get; set; }
public decimal OrderQuantity { get; set; }
public virtual SalesOrderDto SalesOrderHeader { get; set; }
public virtual StockDto MasterStockRecord { get; set; }
}
public class StockDto
{
[Key]
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
}
OData Config:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
I have created an Automapper explicit navigation expansion utility function that should work with N-deph expands. Posting it here since it might help someone.
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
You can call it with :
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
it will return a list with ["Parent" ,"Parent.Child"]
I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.
The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.
The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.
When you want to mark something for explicit expansion in AutoMapper, you need to also opt-back-in when calling ProjectTo<>().
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
While the AutoMapper wiki does state this, the example is perhaps a little misleading by not including the paired ExplicitExpansion() call.
To control which members are expanded during projection, set ExplicitExpansion in the configuration and then pass in the members you want to explicitly expand:

Cannot Parse Json sub object

I am using JObject to parse Json object below
string Jstring = #"{
"PolicyId" :"xxxxxx",
"PolicyHolder" :{"title":"Mr", "FirstName":"test", "LastName":"testLast"}
}";
I can get the PolicyId value through below code
Jobject jobj = Jobject.parse(Jstring);
string PolicyId = jobj.value<string>("PolicyId");
But, I always get null using below code:
string Holder = jobj.value<string>("PolicyHolder");
I have debugged the code below:
jobj.Properties()
I can find PolicyHolder in the List. I have tried code below also, the value is always null
JProperty jproperty = jobj.Properties().SingleOrDefault(x => x.Name == "PolicyHolder");
Can anyone know what happen?
string Holder = jobj.value<string>("PolicyHolder");
Above line fails because PolicyHolder is not string. You are trying to cast it to string. It's an object. You must create a class with the properties of the PolicyHolder and use that as a type instead of string.
Here's the working dotNetFiddle: https://dotnetfiddle.net/xOOl5m
Console Output:
Here are the classes I ended up declaring and using.
public class PolicyHolder
{
public string title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Policy
{
public string PolicyId { get; set; }
public PolicyHolder PolicyHolder { get; set; }
}
Like Sam has mentioned, PolicyHolder is an Object not a string.
Here's the complete code listing (so that the answer is self-contained).
using System;
using System.Linq;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
public class Program
{
// Solution to SO Question: https://stackoverflow.com/q/27159478/325521
// This Answer: https://stackoverflow.com/a/
// Author: Shiva Manjunath
// SO Profile: https://stackoverflow.com/users/325521/shiva
public static void Main()
{
string Jstring = #"{
""PolicyId"" :""xxxxxx"",
""PolicyHolder"" :{""title"":""Mr"", ""FirstName"":""test"", ""LastName"":""testLast""}
}";
JObject jObject = JObject.Parse(Jstring);
JProperty jProperty = jObject.Properties().SingleOrDefault(x => x.Name == "PolicyHolder");
Console.WriteLine("Printing Policy Holder Details....");
Console.WriteLine(jProperty.Value);
}
}
public class PolicyHolder
{
public string title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Policy
{
public string PolicyId { get; set; }
public PolicyHolder PolicyHolder { get; set; }
}

Resources