EF LInq Left outer join sorted taking fist - entity-framework

I have this left outer join query
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => p.LanguageCode).Take(1).DefaultIfEmpty()
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
As you can see I'm getting just one row from the right side of the query while I return all the left rows.
What I need is, instead sorting just by LanguageCode, I need to tell the query to sort by a specifid LanguageCode first and then the rest.
If I add this:
.OrderBy(p => (p.LanguageCode == currentLanguage) ? 0 : 1)
where currentLanguage is just a string containing the language "en", "fr"...
so the query is:
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => (p.LanguageCode == currentLanguage) ? 0 : 1).Take(1).DefaultIfEmpty()
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
I get the following error:
"Unknown column 'Extent1.Id' in 'where clause'"
I need help since I need to return the current language if it exists, if not the next one.
Thanks.
EDIT
The problem is the combination of the ORDER with the TAKE(1), but I do need to take just the first one...
EDIT 2
The probelm seems to be what I commented above...this is the query I'm using:
var queryString = (from entity in _dbContext.Links
from translations in _dbContext.LinksTrs.Where(p => p.LinkId == entity.Id).OrderByDescending(p => p.LanguageCode == currentLanguage).ThenBy(p => p.LanguageCode).Take(1)
select new LinksAdminListModel()
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});
And this is the SQL it is generating...in case someone can see something in here:
SELECT
`Apply1`.`Id`,
`Apply1`.`Name`,
`Apply1`.`Url`,
`Apply1`.`Active`
FROM (SELECT
`Extent1`.`Id`,
`Extent1`.`OrderPos`,
`Extent1`.`Url`,
`Extent1`.`Active`,
`Extent1`.`DateCreated`,
`Extent1`.`DateModified`,
`Extent1`.`UserIdModified`,
(SELECT
`Project1`.`C1`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `C1`,
(SELECT
`Project1`.`Name`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `Name`,
(SELECT
`Project1`.`LanguageCode`
FROM (SELECT
CASE WHEN ((`Extent2`.`LanguageCode` = 'es-ES') OR ((`Extent2`.`LanguageCode` IS NULL) AND ('es-ES' IS NULL))) THEN (1) WHEN (NOT ((`Extent2`.`LanguageCode` = 'es-ES') AND ((CASE WHEN (`Extent2`.`LanguageCode` IS NULL) THEN (1) ELSE (0) END) = (CASE WHEN ('es-ES' IS NULL) THEN (1) ELSE (0) END)))) THEN (0) END AS `C1`,
`Extent2`.`Name`,
`Extent2`.`LanguageCode`
FROM `LinkTr` AS `Extent2`
WHERE `Extent2`.`LinkId` = `Extent1`.`Id`) AS `Project1`
ORDER BY
`Project1`.`C1` DESC,
`Project1`.`LanguageCode` ASC LIMIT 1) AS `LanguageCode`
FROM `Link` AS `Extent1`) AS `Apply1`
EDIT 3
The entities are:
public class Link : EntityBase
{
public int OrderPos { get; set; }
public string Url { get; set; }
public bool Active { get; set; }
public virtual IEnumerable<LinkTr> Translations { get; set; }
}
public class LinkTr : EntityBaseTr
{
public string Name { get; set; }
public string Summary { get; set; }
//[ForeignKey("Link")]
public int LinkId { get; set; }
public virtual Link Link { get; set; }
//[ForeignKey("Language")]
public string LanguageCode { get; set; }
public virtual Language Languages { get; set; }
}
public class EntityBase
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] //[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateModified { get; set; }
public int UserIdModified { get; set; }
}
public class EntityBaseTr
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
}

Cannot duplicate with the information provided.
The only thing I've noticed during the testing was that the expression (translations == null) ? "" : translations.Name was generating null check for Translations table Id column, not sure if you have such in your entity.
In any case, it's worth trying the following alternative which does not include such a check:
var queryString = (
from entity in _dbContext.Links
let translationName = _dbContext.LinksTrs
.Where(p => p.LinkId == entity.Id)
.OrderBy(p => p.LanguageCode == currentLanguage ? 0 : 1)
.Select(p => p.Name)
.FirstOrDefault();
select new LinksAdminListModel()
{
Id = entity.Id,
Name = translationName ?? "",
Url = entity.Url,
Active = entity.Active
});
UPDATE:
It turns out that this is a current MySql EF query provider issue (bug?!).
After playing a while, the only way I was able to make it work is as follows
var queryString =
(from entity in db.Links
let translations =
db.LinksTrs.Where(p => p.LinkId == entity.Id && p.LanguageCode == currentLanguage).FirstOrDefault() ??
db.LinksTrs.Where(p => p.LinkId == entity.Id).OrderBy(p => p.LanguageCode).FirstOrDefault()
select new LinksAdminListModel
{
Id = entity.Id,
Name = (translations == null) ? "" : translations.Name,
Url = entity.Url,
Active = entity.Active
});

Related

What the best way of translating created user name with ef core 3.1

public class Entity
{
public string Id { get; set; }
public string CreatedBy { get; set; }
public string LastUpdatedBy { get; set; }
[NotMapped]
public string Creator { get; set; }
[NotMapped]
public string Updater { get; set; }
}
public class User
{
public string Name { get; set; }
public string Id { get; set; }
}
I am going to search Entities, sort on Creator/Updater properties(and return UserInfo.Name) with ef core query, any idea?
After hours of researches, refers
How do you perform a left outer join using linq extension methods
Entity Framework Join 3 Tables
There 3 ways in oder(presonal perfer the first than second):
class Program
{
static void Main()
{
using TenantDBContext dbContext = new TenantDBContext("PORT=5432;DATABASE=linqtosql;HOST=xxx.com;PASSWORD=xxx;USER ID=postgres;Pooling=true;Minimum Pool Size=10;Application Name=xxx");
var result = (
from entity in dbContext.Entities
join user in dbContext.Users on entity.CreatedBy equals user.Id into temp1
from ce in temp1.DefaultIfEmpty()
join user1 in dbContext.Users on entity.UpdatedBy equals user1.Id into temp2
from cu in temp2.DefaultIfEmpty()
select new Entity() { Id = entity.Id, CreatedBy = entity.CreatedBy, UpdatedBy = entity.UpdatedBy, Creator = ce.Name, Updater = ce.Name }
).ToList();
Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));
var result2 = dbContext.Entities
.GroupJoin(dbContext.Users, e => e.CreatedBy, u => u.Id, (e, u) => new { Entity = e, User = u })
.SelectMany(eUser => eUser.User.DefaultIfEmpty(), (e, u) => new Entity() { Id = e.Entity.Id, CreatedBy = e.Entity.CreatedBy, UpdatedBy = e.Entity.UpdatedBy, Creator = u.Name })
.GroupJoin(dbContext.Users, e => e.UpdatedBy, u => u.Id, (e, u) => new { Entity = e, User = u })
.SelectMany(eUser => eUser.User.DefaultIfEmpty(), (e, u) => new Entity() { Id = e.Entity.Id, CreatedBy = e.Entity.CreatedBy, UpdatedBy = e.Entity.UpdatedBy, Creator = e.Entity.Creator, Updater = u.Name }
).ToList();
Console.WriteLine(JsonConvert.SerializeObject(result2, Formatting.Indented));
var result3 = dbContext.Entities
.SelectMany(entity => dbContext.Users.Where(user => entity.CreatedBy == user.Id).DefaultIfEmpty(), (entity, user) => new { Entity = entity, User = user })
.SelectMany(entity => dbContext.Users.Where(user => entity.Entity.UpdatedBy == user.Id).DefaultIfEmpty(), (entity, user) => new Entity { Id = entity.Entity.Id, CreatedBy = entity.Entity.CreatedBy, UpdatedBy = entity.Entity.UpdatedBy, Creator = entity.User.Name, Updater = user.Name })
.ToList();
Console.WriteLine(JsonConvert.SerializeObject(result2, Formatting.Indented));
}
}

how to serialize entities that have a one to many relationship and a pagination in entity framework?

I have a two Model for two table having foreign key relationship. I have to get record from both table in single request.
The structure of model is as follows:-
EmployeeRecord.cs
public partial class EmployeeRecord
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public tblEmployeeRecord()
{
this.countries = new HashSet<tblCountry>();
}
public int EmployeeId { get; set; }
public string Name { get; set; }
public string userName { get; set; }
public string userRole { get; set; }
public string id { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Country> countries { get; set; }
}
2.Country.cs
public partial class Country
{
public int CountryId { get; set; }
public int EmployeeId { get; set; }
public string CountryName { get; set; }
public virtual EmployeeRecord employeeRecord { get; set; }
}
EmployeeController.cs
public class EmployeeRecordsController : ApiController
{
private EstorageEntitiesforCombineView db = new EstorageEntitiesforCombineView();
// GET: api/EmployeeRecords
public IEnumerable<EmployeeRecord> GetEmployeeRecords([FromUri]PagingParameterModel pagingparametermodel)
{
var source= db.EmployeeRecords.OrderBy(a => a.EmployeeId);
// Get's No of Rows Count
if (pagingparametermodel.userRole == "HR")
{
source= (from a in db.EmployeeRecords
where a.userRole == "HR"
select a).OrderBy(a => a.EmployeeId);
}
else if(pagingparametermodel.userRole == "eStorage Admin")
{
source = (from a in db.EmployeeRecords
where a.userRole == "eStorage Admin"
select a).OrderBy(a => a.EmployeeId);
} else if(pagingparametermodel.userRole == "SharedService")
{
source = (from a in db.EmployeeRecords
where a.userRole == "SharedService"
select a).OrderBy(a => a.EmployeeId);
}
if(pagingparametermodel.toSearch == 1 && pagingparametermodel.userRole == "ALL")
{
source = (from b in db.EmployeeRecords
where b.Name.StartsWith(pagingparametermodel.name)
select b).OrderBy(a => a.EmployeeId);
}else if (pagingparametermodel.toSearch==1) {
source= (from b in db.EmployeeRecords
where b.Name.StartsWith(pagingparametermodel.name) && b.userRole==pagingparametermodel.userRole
select b).OrderBy(a => a.EmployeeId);
}
int count = source.Count();
// Parameter is passed from Query string if it is null then it default Value will be pageNumber:1
int CurrentPage = pagingparametermodel.pageNumber;
// Parameter is passed from Query string if it is null then it default Value will be pageSize:20
int PageSize = pagingparametermodel.pageSize;
// Display TotalCount to Records to User
int TotalCount = count;
// Calculating Totalpage by Dividing (No of Records / Pagesize)
int TotalPages = (int)Math.Ceiling(count / (double)PageSize);
// Returns List of Customer after applying Paging
var items = source.Skip((CurrentPage - 1) * PageSize).Take(PageSize).ToList();
// if CurrentPage is greater than 1 means it has previousPage
var previousPage = CurrentPage > 1 ? "Yes" : "No";
// if TotalPages is greater than CurrentPage means it has nextPage
var nextPage = CurrentPage < TotalPages ? "Yes" : "No";
// Object which we are going to send in header
var paginationMetadata = new
{
totalCount = TotalCount,
pageSize = PageSize,
currentPage = CurrentPage,
totalPages = TotalPages,
previousPage,
nextPage
};
// Setting Header
System.Web.HttpContext.Current.Response.Headers.Add("Paging-Headers", Newtonsoft.Json.JsonConvert.SerializeObject(paginationMetadata));
// Returing List of Customers Collections
return items;
}
}
I want the response as follows :-
[{
"tblCountries": [
{
"CountryId": 265,
"EmployeeId": 350,
"CountryName": "INWEST"
}
],
"EmployeeId": 350,
"Name": "ABC",
"userName": "abc#mail.com",
"userRole": "HR",
"id": nbh546652n45
}]
But the response is as follows:-
[{
"tblCountries": [],
"EmployeeId": 350,
"Name": "ABC",
"userName": "abc#mail.com",
"userRole": "HR",
"id": nbh546652n45
}]
I have also tried, Include(a => a.countries) as follows:-
source= (from a in db.EmployeeRecords
where a.userRole == "HR"
select a).Include(a => a.countries).OrderBy(a => a.EmployeeId);
But i am getting following error:-
<ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Object graph for type 'System.Collections.Generic.HashSet`1[[eStorageApi.Models.tblCountry, eStorageApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled.
</ExceptionMessage>
<ExceptionType>
System.Runtime.Serialization.SerializationException
</ExceptionType>
Tried including these LOCs into webApiConfig.cs:-
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
Still getting the same error.
Project your domain model into something else before returning it from your api. This better controls the response data format and if a property gets added (such as ssn) it doesn't get inadvertently exposed. This also has the added benefit of fixing your cyclical references.
return items.Select(i => new {
i.Name,
i.Username,
...
Countries = i.Countries.Select(c => new {
c.CountryId,
c.CountryName,
...
}
};
You need to do the relationship between employeeRecord and countries. Try with this:
source= db.EmployeeRecords.Include(e => e.countries).Select(c => c.employeeRecord)
.Where(a => a.userRole == "HR");
And when you do the request you need specify text: Application/Json

Entity Framework Core Select Outer Join

is there an easy way to include a nullable navigation inside a select expression for EF Core?
My model looks like this
public class RootVO
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Description { get; set; }
public int? RelationId { get; set; }
[ForeignKey(nameof(RelationId))]
public RelationVO Relation { get; set; }
}
public class RelationVO
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Property1 { get; set; }
[Required]
[StringLength(200)]
public string Property2 { get; set; }
public ICollection<RootVO> RootRelations { get; set; }
}
When I load the data I just want to select certain kind of properties. Currently my expression looks like this:
Expression<Func<RootVO, RootVO>> selectExpr = m => new RootVO
{
Id = m.Id,
Description = m.Description,
Relation = m.Relation != null ? new RelationVO
{
Id = m.Relation.Id,
Property1 = m.Relation.Property1
} : null
};
var result = context.Roots.Select(selectExpr).ToList();
Is there an easier way to handle the relation select?
Edit
Maybe some background here will help:
I have a huge object with a lot of columns and relations, some with inner, some with outer joins. This query gets accessed by a datagrid on UI which can have dynamic columns depending on the user selection. To increase the performance I've written a class that will build the select expression dynamicly depending on the selected columns. For now it is working, but I'm having trouble when an outer join is null due to null-reference excepction.
The debug view on the expression could look like this:
.New IVA.Core.Data.Models.StockMovementLogVO(){
SequenceNo = $m.SequenceNo,
PostingPeriodId = $m.PostingPeriodId,
TransactionDate = $m.TransactionDate,
FinancialYear = $m.FinancialYear,
FinancialYearPeriod = $m.FinancialYearPeriod,
VoucherDate = $m.VoucherDate,
ItemQuantity = $m.ItemQuantity,
BuCode = $m.BuCode,
LocationStructure = .New IVA.Core.Data.Models.LocationStructureVO(){
Id = ($m.LocationStructure).Id,
Description = ($m.LocationStructure).Description
},
BookingType = .New IVA.Core.Data.Models.BookingTypeVO(){
Id = ($m.BookingType).Id,
Description = ($m.BookingType).Description
},
PartnerStockLocationType = .New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.PartnerStockLocationType).Id,
Description = ($m.PartnerStockLocationType).Description
},
StockLocationType = .New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.StockLocationType).Id,
Description = ($m.StockLocationType).Description
}
}
StockLocationType and PartnerStockLocationType are outer joins and if those are null the query fails to execute.
I've now changed my expression builder that it will take care of the outer joins by including a null reference check. The expression now looks like this:
.New IVA.Core.Data.Models.StockMovementLogVO(){
SequenceNo = $m.SequenceNo,
PostingPeriodId = $m.PostingPeriodId,
TransactionDate = $m.TransactionDate,
FinancialYear = $m.FinancialYear,
FinancialYearPeriod = $m.FinancialYearPeriod,
VoucherDate = $m.VoucherDate,
ItemQuantity = $m.ItemQuantity,
BuCode = $m.BuCode,
LocationStructure = .New IVA.Core.Data.Models.LocationStructureVO(){
Id = ($m.LocationStructure).Id,
Description = ($m.LocationStructure).Description
},
BookingType = .New IVA.Core.Data.Models.BookingTypeVO(){
Id = ($m.BookingType).Id,
Description = ($m.BookingType).Description
},
PartnerStockLocationType = .If ($m.PartnerStockLocationType != null) {
.New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.PartnerStockLocationType).Id,
Description = ($m.PartnerStockLocationType).Description
}
} .Else {
null
},
StockLocationType = .If ($m.StockLocationType != null) {
.New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.StockLocationType).Id,
Description = ($m.StockLocationType).Description
}
} .Else {
null
}
}
Edit
If anyone is interessted how it looks, I've created a repository where I use the class.
https://github.com/NQuirmbach/DynamicQueryBuilder

Project into a (list of) concrete object instead of an (list of) anonymous object

The problem simplified is as follows: in Entity Framework i am doing a join involving 3 tables, and returning the joined result set, which involves (some) fields from the 3 tables.
var query = (
from t1 in dbCtx.TB_Entity1
from t2 in dbCtx.TB_Entity2
.Where(p => p.someCol == t1.someCol && t.IsActive == true)
.DefaultIfEmpty() //LEFT JOIN
from t3 in dbCtx.TB_Entity3
.Where(q => q.someCol == t2.someCol)
where t1.IsLatest == true
&& (t1.istatus == 2
|| t1.istatus == 3
)
select new {
t1.col100,
t1.col101,
t2.col200,
t2.col201,
t3.col300,
t3.col301
}).OrderByDescending(t1 => t1.ID);
var anonObjList = query.ToList();
Thus, at the end of the query I write a projection to select the fields i want.
Finally i run the query with .ToList() and get a list of Anonymous objects.
How do i modify the query to project into a List of MyConcreteClass
i.e. i want to be able to write something similar to
List<MyConcreteClass> myObjList = query.ToList();
You may assume my concrete class looks like
public class MyConcreteClass
{
public string Col100 { get; set; }
public string Col101 { get; set; }
public string Col200 { get; set; }
public string Col201 { get; set; }
public string Col300 { get; set; }
public string Col301 { get; set; }
}
You just use the object initializer syntax:
new MyConcreteClass
{
Col100 = t1.col100,
Col101 = t1.col101,
Col200 = t2.col200,
Col201 = t2.col201,
Col300 = t3.col300,
Col301 = t3.col301
}

How to call object from Controller to display in the View of ASP.net MVC

I have one action in my controller :
public ActionResult GiftVochure()
{
if (Request.QueryString["gc"] != "")
{
var obj = from b in context.GiftCards
join cus in context.Customers on b.CustomerID equals cus.ID
where b.ID == int.Parse(Request.QueryString["gc"])
select new
{
b.ID,
b.Date,
b.CardNo,
b.Price,
CustomerName = cus.FirstName + " " + cus.LastName
};
return View(obj.ToList());
}
return View();
}
And I want to take "obj" to loop and display in the GiftVochure Views, Does any one know, how to do this?
Thanks.
You should start by defining a model type to replace the anonymous type projected by the query.
public class CardInfo
{
int ID { get; set; }
DateTime Date { get; set; }
int CardNo { get; set; }
double Price { get; set; }
string CustomerName { get; set; }
}
Modifying your action method:
var obj = from b in context.GiftCards
join cus in context.Customers on b.CustomerID equals cus.ID
where b.ID == int.Parse(Request.QueryString["gc"])
select new CardInfo
{
ID = b.ID,
Date = b.Date,
CardNo = b.CardNo,
Price = b.Price,
CustomerName = cus.FirstName + " " + cus.LastName
};
return View(obj);
Then you should strongly type your GiftVochure view to this type's sequence.
#model IEnumerable<CardInfo>
In the end you can iterate the items in your view.
#foreach(CardInfo current in Model) {
//display info
}
You should probably move your markup for displaying a single object to a partial view. Then render it in your loop.