Dynamically paging EF Core results - .net-core

I have a UI grid which permits sorting by column :
Id
Organisation Name
Organisation type
Departments
1
first
some type
def
2
second
another type
abc, def
2
third
some type
xyz
See the entities below:
public class Organisation
{
public int Code { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public List<Department> Departments { get; set; }
}
public class Department
{
public int Code { get; set; }
public string Name { get; set; }
}
I want to be able to sort the table values by Departments which is a comma separated values that comes from Organization.Departments.Select(p=> p.Name);
I would like to make the sorting as an IQueryable and avoid bringing all the data in memory because After sorting I will apply the pagination and I don't want to bring all the DB records in memory.
I'm using the following extension method for sorting, but it is not working for nested collections:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortProperty, ListSortDirection sortOrder)
{
var type = typeof(T);
var property = type.GetProperty(sortProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (property == null)
throw new OperationFailedException($"Sorting by {sortProperty}");
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var typeArguments = new Type[] { type, property.PropertyType };
var methodName = sortOrder == ListSortDirection.Ascending ? "OrderBy" : "OrderByDescending";
var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
This method works fine for properties that are at object level.
IQueryable I'm using later for sorting looks something like this:
var iQueryableToBeSorted = _dbContext.Organization.Include(p=>p.Departments).AsQueryable();

Related

.Net Core 6.0 Web API - How to implement postgresql database(eg: Product Table -> Description column) localization for English and French?

I am developing a Web API using Core 6.0 with localization. Localization should be supported for both static (e.g., basic strings like greeting) and dynamic content (e.g., Values of the Product Instance).
I have implemented the localization for static content using JsonStringLocalizerFactory as discussed in this article - https://christian-schou.dk/how-to-add-localization-in-asp-net-core-web-api/.
public class LocalizerController : ControllerBase
{
private readonly IStringLocalizer<LocalizerController> _stringLocalizer;
public LocalizerController(IStringLocalizer<LocalizerController> stringLocalizer)
{
_stringLocalizer = stringLocalizer;
}
[HttpGet]
public IActionResult Get()
{
var message = _stringLocalizer["hi"].ToString();
return Ok(message);
}
[HttpGet("{name}")]
public IActionResult Get(string name)
{
var message = string.Format(_stringLocalizer["welcome"], name);
return Ok(message);
}
[HttpGet("all")]
public IActionResult GetAll()
{
var message = _stringLocalizer.GetAllStrings();
return Ok(message);
}
}
Next, I would like to implement localization for dynamic content (e.g., Details of the Product which will be sent to the WEB API and stored in the postgresql database table).
A possible approach is to duplicate the postgresql database table for each language (English and French). Could there be a better approach to avoid duplicate data and additional manual work?
You can create language table for each multi-language entity.
Langugage model;
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
Static language list;
public class Constant
{
public static List<Language> Languages { get; set; } = new()
{
new Language
{
Id = 1,
Name = "English(United States)",
IsoCode = "en-US"
},
new Language
{
Id = 2,
Name = "Turkish",
IsoCode = "tr-TR"
}
};
}
Entities;
public class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ProductLang> ProductLangs { get; set; }
}
public class ProductLang
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[ForeignKey("Products")]
public Guid ProductId { get; set; }
public virtual Product Product { get; set; }
public int LanguageId { get; set; }
}
You can change the LanguageId property name. If you want to store languages in database, you can create a Languages table and create a relationship with that table from entity language tables. This can reduce duplication.
After include the language table to the entity, you can write an extension method to easily get the requested language data.
public static string GetLang<TEntity>(this IEnumerable<TEntity> langs, Expression<Func<TEntity, string>> propertyExpression, int defaultLangId)
{
var languageIdPropName = nameof(ProductLang.LanguageId);
var requestedLangId = GetCurrentOrDefaultLanguageId(defaultLangId);
if (langs.IsNullOrEmpty())
return string.Empty;
var propName = GetPropertyName(propertyExpression);
TEntity requestedLang;
if (requestedLangId != defaultLangId)
requestedLang = langs.FirstOrDefault(lang => (int)lang.GetType()
.GetProperty(languageIdPropName)
.GetValue(lang) == requestedLangId)
?? langs.FirstOrDefault(lang => (int)lang.GetType()
.GetProperty(languageIdPropName)
.GetValue(lang) == defaultLangId);
else requestedLang = langs.FirstOrDefault(lang => (int)lang.GetType().GetProperty(languageIdPropName).GetValue(lang) == defaultLangId);
requestedLang ??= langs.FirstOrDefault();
return requestedLang.GetType().GetProperty(propName).GetValue(requestedLang, null)?.ToString();
static int GetCurrentOrDefaultLanguageId(int defaultLanguageId)
{
var culture = CultureInfo.CurrentCulture;
var currentLanguage = Constant.Languages.FirstOrDefault(i => i.IsoCode == culture.Name);
if (currentLanguage != null)
return currentLanguage.Id;
else
return defaultLanguageId;
}
static string GetPropertyName<T, TPropertyType>(Expression<Func<T, TPropertyType>> expression)
{
if (expression.Body is MemberExpression tempExpression)
{
return tempExpression.Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}
}
This extension method checks for 3 conditions;
If there is data in the requsted language, it returns this data,
If there is no data in the requsted language, it checks if there is data in the default language. If the data is available in the default language, it will return the data,
Returns the first available language data if there is no data in the default language
Usage;
var defaultLangId = 1;
Product someProduct = await _dbContext.Set<Product>().Include(i => i.ProductLangs).FirstOrDefaultAsync();
var productName = someProduct.ProductLangs.GetLang(i => i.Name, defaultLangId);
It is up to you to modify this extension method according to your own situation. I gave you an example scenario where languages are kept in a static list.

System.Text.Json Deserialize Fails

With this DTO:
public class QuestionDTO {
public Guid Id { get; set; }
public string Prompt { get; set; }
public List<Answer> Choices { get; set; }
public QuestionDTO() {
}
public QuestionDTO(Question question) {
this.Id = question.Id;
this.Prompt = question.Prompt;
this.Choices = question.Choices;
}
}
I was getting an error about Unable to Parse without a parameterless constructor. I have since fixed that, but now my objects are de-serialized empty:
using System.Text.Json;
var results = JsonSerializer.Deserialize<List<QuestionDTO>>(jsonString);
The jsonString contains 3 items with the correct data, and the deserialized list contains 3 items, but all the properties are empty.
The new json library is case sensitive by default. You can change this by providing a settings option. Here is a sample:
private JsonSerializerOptions _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
private async Task SampleRequest()
{
var result = await HttpClient.GetStreamAsync(QueryHelpers.AddQueryString(queryString, queryParams));
_expenses = await JsonSerializer.DeserializeAsync<List<Common.Dtos.Expenses.Models.Querys.ExpensesItem>>(result, _options);
}

Object value is null after deserialization (Xamarin with SQLite)

for an internship project I'm developping an app with Xamarin that will allow users to scan barcodes and create sheets of labels and purchases.
To prevent the scanned codes from being lost in case of crash etc, I've implemented SQLite to create a local backup that we could restore.
The structure is as follows : a ListOfLabelLines contains several LabelLine which each contain a Product and different other informations (such as packaging, quantity etc).
ListOfLabelLines.cs :
[Table("ListOfLabelLines")] // Indique le nom de la table qui sera générée par SQLite
public class ListOfLabelLines : BaseItem
{
private string _name { get; set; }
[TextBlob("LabelLinesBlob")]
public ObservableCollection<LabelLine> lines { get; set; }
[TextBlob("ListBlob")]
public List<String> TestList { get; set; }
public string LabelLinesBlob { get; set; } // serialized LabelLines
public string ListBlob { get; set; } // serialized TestList
public ListOfLabelLines()
{
}
public ListOfLabelLines(string name)
{
this._name = name;
lines = new ObservableCollection<LabelLine>();
TestList = new List<String>();
TestList.Add("Test1");
TestList.Add("Test2");
}
public string Name
{
get { return _name; }
set
{
_name = value;
}
}
}
}
These objects ListOfLabelLines contain an ObservableCollection<LabelLine> which I'm serializing by using the TextBlob property from SQLite-net-extensions.
However, when I retrieve the ListOfLabelLines I've stored, the ObservableCollection appears as null :
Example of null collections
Here are the methods I use to store the objects in SQlite :
public void SaveListOfLabelLines(ListOfLabelLines ShelfLabelInstance)
{
var query = from label in database.Table<ListOfLabelLines>()
where label.Name == ShelfLabelInstance.Name
select label;
var res = query.FirstOrDefault();
if (res != null)
{
database.UpdateWithChildren(ShelfLabelInstance);
Console.WriteLine("Label " + ShelfLabelInstance.Name + " updated");
}
else
{
database.InsertWithChildren(ShelfLabelInstance);
Console.WriteLine("Label " + ShelfLabelInstance.Name + " created");
}
}
and to retrieve them :
public void CheckProductsInLabelLine(string n)
{
var query = from LOLL in database.Table<ListOfLabelLines>()
where LOLL.Name == n
select LOLL;
ListOfLabelLines res = query.FirstOrDefault();
The string property linked to the TextBlob, however, contains the JSON object I need.
I thought the ObservableCollection would be obtainable when getting the object in DB since TextBlob is supposed to serialize AND deserialize.
Could anybody help ?
Thanks a lot !

A circular reference was detected while serializing entities with one to many relationship

How to solve one to many relational issue in asp.net?
I have Topic which contain many playlists.
My code:
public class Topic
{
public int Id { get; set; }
public String Name { get; set; }
public String Image { get; set; }
---> public virtual List<Playlist> Playlist { get; set; }
}
and
public class Playlist
{
public int Id { get; set; }
public String Title { get; set; }
public int TopicId { get; set; }
---> public virtual Topic Topic { get; set; }
}
My controller function
[Route("data/binding/search")]
public JsonResult Search()
{
var search = Request["term"];
var result= from m in _context.Topics where m.Name.Contains(search) select m;
return Json(result, JsonRequestBehavior.AllowGet);
}
When I debug my code I will see an infinite data because Topics will call playlist then playlist will call Topics , again the last called Topic will recall playlist and etc ... !
In general when I just use this relation to print my data in view I got no error and ASP.NET MVC 5 handle the problem .
The problem happens when I tried to print the data as Json I got
Is there any way to prevent an infinite data loop in JSON? I only need the first time of data without call of reference again and again
You are getting the error because your entity classes has circular property references.
To resolve the issue, you should do a projection in your LINQ query to get only the data needed (Topic entity data).
Here is how you project it to an anonymous object with Id, Name and Image properties.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you have a view model to represent the Topic entity data, you can use that in the projection part instead of the anonymous object
public class TopicVm
{
public int Id { set;get;}
public string Name { set;get;}
public string Image { set;get;}
}
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new TopicVm
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you want to include the Playlist property data as well, you can do that in your projection part.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image,
Playlist = x.Playlist
.Select(p=>new
{
Id = p.Id,
Title = p.Title
})
});
return Json(result, JsonRequestBehavior.AllowGet);
}

Is it possible to get populate class object from xml?

I have an xml file like this:
<CommissionTypes>
<Type>CPA</Type>
<Lender>
<Seq>001</Seq>
<PostUrl>http://www.mysite.com</PostUrl>
</Lender>
</CommissionTypes>
</Lenders>
Having got the data like this:
var config = XDocument.Load(xml);
I need to map it to a class collection, the class is structured like this:
public class Lender
{
public string Type { get; set; }
public int Seq { get; set; }
public string PostUrl { get; set; }
public void Lender(string type, int seq, string postUrl)
{
Type = type;
Seq = seq;
PostUrl = postUrl;
}
}
I've been trying to do this for some time using linq, but without success as yet. What i want to do is retrieve all lenders with in the type "CPA" or any other type.
Any advice?
// * UPDATE * //
The update below is where I'm currently at. Its not working, getting a 'object reference not set to an instance' error wgere arrow is.
<CommissionTypes>
<Type type="CPA">
<Lender>
<Seq>001</Seq>
<PostUrl>http://www.mysite.com</PostUrl>
</Lender>
</Type>
</CommissionTypes>
public static List<Lender> GetLenders(string xml)
{
var myXml = XDocument.Load(xml);
var lenders = new List<Lender>();
lenders = (from type in myXml.Descendants("Type")
where type.Attribute("type").Value == "CPA"
===> select new Lender(
"CPA",
type.Element("Seq").Value.ConvertTo<int>(),
type.Element("PostUrl").Value)).ToList();
return lenders;
}
Your query is incorrect, because Seq is not a direct child of Lender element.
public static List<Lender> GetLenders(string xml)
{
var myXml = XDocument.Load(xml);
var lenders = new List<Lender>();
lenders = (from type in myXml.Descendants("Type")
where type.Attribute("type").Value == "CPA"
from lender in type.Elements("Lender")
select new Lender(
"CPA",
(int)lender.Element("Seq"),
(string)lender.Element("PostUrl"))).ToList();
return lenders;
}

Resources