Handle POST requests with many-to-many relationships ASP.NET EFCore - asp.net

I have these classes
public class Project
{
public Guid Id { get; set; }
public string DeployUrl { get; set; }
public List<Technology> Techs { get; set; } = new();
}
public class Technology
{
public Guid Id { get; set; }
public string Name { get; set; }
public string TechImg { get; set; }
[JsonIgnore]
public List<Project> Projects { get; set; } = new();
[JsonIgnore]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
Which creates a many-to-many relationship. I want to add an endpoint to create a Project. The main issue here is the approach with which i'll do it. Since I don't send the full Technology object to the frontend, it would be impossible to send back a full Project as the request body for the endpoint since its Technology property would be missing fields, which posses an incompatibility with the actual entity in my server. Of course that the Technology instance already exists. So, what is the best approach here? Currently I'm sending a Project DTO that doesn't contain a List<Technology> but a List<Guid> of technologies, and in my repository I fetch from the DB all those technologies and add them manually. Is this correct? Thanks in advance.
public class ProjectPostDto
{
public string DeployUrl { get; set; }
public List<Guid> TechsIds { get; set; } = new();
}
public async Task<ActionResult> PostProject(ProjectPostDto projectDto)
{
try
{
Project newProject = FromPostDto(projectDto);
newProject.Techs = await Service.GetTechs(projectDto.TechsIds);
await Service.Create(newProject);
ProjectPostDto newProjectDto = ToPostDto(newProject);
return CreatedAtAction(nameof(GetProject), new { id = newProject.Id }, newProjectDto);
} catch (Exception)
{
return StatusCode(500, "Server error");
}
}

Related

Xamarin Forms SQLLite add OneToOne and OneToMany subobjects to db

Im trying to create a persistent sqllite db (creation tables once you install the app, deletion of db when you uninstall app)
I have a issue that I cant save my sub objects for example
public class ObjectInstanceResponseModel : GenericResponseModel
{
public ObservableCollection<ObjectInstanceData> ObjectInstances { get; set; }
}
public class ObjectInstanceData : GenericResponseModel
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[JsonProperty("idObjectinstance")]
public int IdObjectInstance { get; set; }
[JsonProperty("objectclass_idObjectclass")]
public int ObjectClassIdObjectClass { get; set; }
[JsonProperty("objectclassname")]
public string ObjectClassName { get; set; }
[JsonProperty("visibilitylevel")]
public int VisibilityLevel { get; set; }
[JsonProperty("showname")]
public bool ShowName { get; set; }
[JsonProperty("showicon")]
public bool ShowIcon { get; set; }
[JsonProperty("creationtime")]
public DateTimeOffset CreationTime { get; set; }
[JsonProperty("users_idUsers")]
public int UsersIdUsers { get; set; }
[JsonProperty("isfavorite")]
public bool? IsFavorite { get; set; }
[OneToMany("ObjectInstanceDataStrings")]
[JsonProperty("strings")]
public List<String> Strings { get; set; }
}
public class String
{
[PrimaryKey, AutoIncrement]
[JsonProperty("idObjectparameterstring")]
public int? IdObjectParameterString { get; set; }
[ForeignKey(typeof(ObjectInstanceData))]
public int ObjectInstanceDataStrings { get; set; }
[JsonProperty("stringvalue")]
public string StringValue { get; set; }
[JsonProperty("objectparameterset_idObjectparameterset")]
public int? ObjectParameterSetIdObjectParameterSet { get; set; }
[JsonProperty("showinballon")]
public bool? ShowInBallon { get; set; }
[JsonProperty("idClassparameter")]
public int IdClassParameter { get; set; }
[JsonProperty("classparametername")]
public string ClassParameterName { get; set; }
}
So, my class String is always empty, although there are some rows in the table that I created Strings..
Am I need a lazy loading for this?
I implemented sqllite through depedency service in my app.cs like this:
public partial class App : Application
{
public static SQLiteConnection DatabaseConnection { get; set; }
public App()
{
InitializeComponent();
DatabaseConnection = DependencyService.Get<IConnection>().GetConnection();
CreateTables();
}
private void CreateTables()
{
DatabaseConnection.CreateTable<ObjectInstanceData>();
DatabaseConnection.CreateTable<Models.Objects.String>();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
So, basically the logic should be when no internet, work with sql lite (keep local changes), and when internet come back upload that changes that kept in db, and erase data from tables.
You noticed that Im using response model for api.
So, Im calling from my FavoriteObjectViewModel this:
var response = await ApiServiceProvider.GetObjectInstances(null, true);
and in the ApiServiceProvider:
public static async Task<ObjectInstanceResponseModel> GetObjectInstances(string queryString = null, bool? onlyFavorites = null)
{
HttpResponseMessage response;
response = await apiClient.GetAsync(objectInstancesEndpoint);
var resultContent = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ObservableCollection<ObjectInstanceData>>(resultContent);
objectInstanceResponse.ObjectInstances = result;
//after each api call I need to update db
//delete previous data, and add fresh data from api
App.DatabaseConnection.DeleteAll<ObjectInstanceData>();
foreach (var item in result)
{
App.DatabaseConnection.Insert(item);
if (item.Strings != null && item.Strings.Count > 0)
App.DatabaseConnection.InsertAll(item.Strings);
}
//I only get the data for ObjectInstanceData, Strings model is empty!
var objectsResponseDb = App.DatabaseConnection.GetAllWithChildren<ObjectInstanceData>();
objectInstanceResponse.Succeeded = true;
return objectInstanceResponse;
}
So, my questions are:
If I create tables each time in App.cs is that mean, that I not store data, when user quit application, and re-enter again?
Why is the model Strings empty? When I invoke var strings = App.DatabaseConnection.GetAllWithChildren<Models.Objects.String>(); I can see that there is data?
What is the best approach for doing offline "logging"? maybe there is a some better nuget for sqllite?
I don't know much about persistent databases but one thing I can tell you for sure:you're approach is wrong.
You should create a separate class for the database's logic like creating tables and instantiating the connection along with other methods for manipulating data.
In the App class you can create a static resource of the database class which you can call it and make use of the methods in it.
I'm not very good with explaining but here it is a very basic example of how it should look:https://learn.microsoft.com/en-us/xamarin/get-started/tutorials/local-database/?tutorial-step=2&tabs=vswin.

How to send a collection of files + JSON data to the client side

In my ASP.NET Core Web API I have an entity Idea:
public class Idea
{
public int Id { get; set; }
public string Name { get; set; }
public string OwnerId { get; set; }
public User Owner { get; set; }
public string Description { get; set; }
public int? MainPhotoId { get; set; }
public Photo MainPhoto { get; set; }
public int? MainVideoId { get; set; }
public Video MainVideo { get; set; }
public ICollection<Photo> Photos { get; set; }
public ICollection<Video> Videos { get; set; }
public ICollection<Audio> Audios { get; set; }
public ICollection<Document> DocumentsAboutTheIdea { get; set; }
public DateTime DateOfPublishing { get; set; }
}
public class Photo
{
public int Id { get; set; }
public string Url { get; set; }
}
(the different media-type classes are equivalent)
When the client makes a Post request for creating the Idea he sends the information about it along with all the media-files (I am using IFormFile and IFormFileCollection) and while saving them on the server I set the Url property to match their location. But in the Get request I want to send the files (not Urls).
Here is the Get action which now returns only one file (mainPhoto) without any JSON and without any other media-files:
[HttpGet("{id}", Name = "Get")]
public async Task<IActionResult> Get(int id)
{
var query = await unitOfWork.IdeaRepository.GetByIdAsync(id, includeProperties: "Owner,MainPhoto,MainVideo,Photos,Videos,Audios,DocumentsAboutTheIdea");
if (query != null)
{
string webRootPath = hostingEnvironment.WebRootPath;
var path = string.Concat(webRootPath, query.MainPhoto.Url);
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, GetContentType(path), Path.GetFileName(path));
}
return NotFound();
}
So in a Get request I want to send to the client side (an Angular app) some of the information about the Idea in a JSON-format ALONG WITH different (NOT ONE) media-files associated with it. My goal is to make the client side get it all so that it can then show them on the page of the info about the Idea. But I cannot figure out the approach for this. Is there a way to achieve this? Or there is another (better) way of interacting with the client side to transfer all the required data? I tried to find information on this topic but couldn't find any.

Using Firesharp in Xamarin.Forms to get Data from Firebase Database

I want to push and get Recipe-Data from my Firebase Database in Xamarin.Forms with the Firesharp Plugin.
My Model Class is the Recipe Class:
public class Recipe
{
public string title { get; set; }
public string workTime { get; set; }
public string degreeOfDifficulty { get; set; }
public string caloriesPerPerson { get; set; }
public string preparation { get; set; }
public string cookingBakingTime { get; set; }
public string restTime { get; set; }
public int portions { get; set; }
public string pictureSource { get; set; }
public List<Ingredient> ingredients { get; set; }
public Recipe()
{
ingredients = new List<Ingredient>();
}
}
So Push Recipe-Objects to the Firebase DB works:
public async Task PushRecipe(Recipe recipe)
{
IFirebaseClient client = new FirebaseClient(config);
client.Push("Recipes", recipe);
}
Firebase Example
But when i want to get Data from the Database i get an Error..
public async Task<List<Recipe>> GetAllRecipes()
{
IFirebaseClient client = new FirebaseClient(config);
FirebaseResponse response = await client.GetAsync("Recipes");
try
{
List<Recipe> recipelist = response.ResultAs<List<Recipe>>();
return recipelist;
} catch (Exception e){
}
return null;
}
After this:
List<Recipe> recipelist = response.ResultAs<List<Recipe>>();
this Exception comes..
"{Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[dignaBon_App.Models.Recipe]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correc…}"
I don´t understand why i can´t get Data from the Database back..
Can someone help me?
You need instatiate the "config" with your firebase secret address key. Go to your Firebase console for this.

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:

Create Dapper Repository Method for Complex Data

I want to create a generic repository method for the complex data(Data Result of multiple joins in database). Following are the classes which hold the data . The data comes from SQL is the join of three tables(Tables architecture is same as of class)
public class InterfaceHeaderSetting
{
public Guid Id { get; set; }
public string CodaDocCode { get; set; }
public string Company { get; set; }
public string Currency { get; set; }
public string DocDescription { get; set; }
public Screen Screen { get; set; }
public Interface Interface { get; set; }
}
public class Screen
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Interface
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I have the repository methods like
public IEnumerable<T> GetAllDynamic(string sql)
{
return Connection.Query<T>(sql, commandType: CommandType.StoredProcedure).ToList();
}
public T Update(T entity, string sql, object dynamicParameters)
{
return
Connection.Query<T>(sql, dynamicParameters, commandType: CommandType.StoredProcedure).SingleOrDefault();
}
I want one more repository method by which I can fill the objects like InterfaceHeaderSetting object.
I don't think you should reinvent the wheel. For this type of function i think Entity Framework is the solution.
In my projects i let entity framework handle more advanced querys that need joins and let dapper do the simple insert, update and select jobs.

Resources