LinqToDb: Rank is server-side method - c#

I am trying to use linq2db.EntityFrameworkCore for some of its windowing functions, such as RANK().
Below is my implementation:
var abc = (from tl in _repo.Context.TransferLink
join tlt in _repo.Context.TransferLinkType on new { TLinkId = tl.TransferLinkTypeId, EType = "Deviance" } equals new { TLinkId = tlt.TransferLinkTypeId, EType = tlt.EnumTransferLinkType }
//let duplicateCount = _repo.Context.TransferLink.Where(tl1 => tl1.SecondaryTransferId != null && tl.SecondaryTransferId != null &&
//tl1.SecondaryTransferId == tl.SecondaryTransferId.Value).Count()
where
(allTransferIds.Contains(tl.PrimaryTransferId) || allTransferIds.Contains(tl.SecondaryTransferId)) &&
!tl.Archived
select new
{
TransferLinkId = tl.TransferLinkId,
TransferLinktypeId = tl.TransferLinkTypeId,
PrimaryTransferId = tl.PrimaryTransferId,
SecondaryTransferId = tl.SecondaryTransferId,
DuplicateCount = Sql.Ext.Count(tl.TransferLinkId)
.Over()
.PartitionBy(tl.SecondaryTransferId)
.ToValue()
UpdatedDate = tl.UpdatedDate,
RankVal = Sql.Ext.Rank()
.Over()
.PartitionBy(tl.TransferLinkTypeId, tl.SecondaryTransferId)
.OrderByDesc(tl.UpdatedDate)
.ThenBy(tl.TransferLinkId)
.ToValue()
}).ToList();
This code throws the exception:
Rank is server-side method
I have tried searching for a solution, but could not find any.
Any idea?

For using linq2db.EntityFrameworkCore you have to switch to library's LINQ provider. It can be done by simple ToLinqToDB() call.
var query = /* some EF Core query */
query = query.ToLinqToDB();
var result = query.ToList();

Related

How to change the entity to linq

I want to change this code from entity to linq.
I always get the error "value cannot be null. parameter name: text"
how to fix that error
public ActionResult Search(SearchView search)
{
IEnumerable<Transaction> thunhaps = null, chitieus = null;
decimal totalTN = 0, totalCT = 0;
int userid = Convert.ToInt32(Session["userid"]);
//var trans = new List<Transaction>();
QuanLyChiTieuDataContext context = new QuanLyChiTieuDataContext();
List<Transaction> trans = new List<Transaction>();
if (search.MoTa != null)
search.MoTa = "";
if (search.TransType == "i" || search.TransType == "b")
{
thunhaps = from item in context.ThuNhaps
where item.UserID == userid
&& item.MoTa.Contains(search.MoTa)
&& item.Ngay.Value >= search.TuNgay &&
item.Ngay <= search.DenNgay
select new Transaction
{
Id = item.Inc_Id,
SoTien = (decimal)item.SoTien,
MoTa = item.MoTa,
GhiChu = item.GhiChu,
NgayGD = item.Ngay.Value,
TransType = "Income"
};
totalTN = thunhaps.Sum(t => t.SoTien);
//List<ThuNhap> thuNhaps = new List<ThuNhap>();
//var totalTNhap = thuNhaps.Sum(t => t.SoTien);
}
if (thunhaps != null)
trans.AddRange(thunhaps);
ViewBag.Difference = totalTN - totalCT;
return PartialView("SearchResult", trans);
}
I very hard to try the change but I failed`
When asking questions in StackOverflow you should be clearer.
A working sample would make a lot easier for anyone to help you.
That being said, you probably get the error because you are querying a recent created context object (and context.ThuNhaps is probably empty, and item.MoTa is null and item.Ngay is null, ...).
Now, I don't know if your question is about the error or "change the entity to linq", which I think you meant change the syntax of LINQ being used, from Query (Comprehension) Syntax to Lamda (Method) Syntax.
Something like:
thunhaps = context.ThuNhaps
.Where(item =>
item.UserID == userid
&& item.MoTa.Contains(search.MoTa)
&& item.Ngay.Value >= search.TuNgay
&& item.Ngay <= search.DenNgay)
.Select(item =>
new Transaction
{
Id = item.Inc_Id,
SoTien = (decimal)item.SoTien,
MoTa = item.MoTa,
GhiChu = item.GhiChu,
NgayGD = item.Ngay.Value,
TransType = "Income"
});

Cannot convert Linq to SQL

So for a assignement I got I have to convert the data I get through an API into a object and save that object in the database. Since the object doesn't have a any property that is unique there are multiple primary key columns in my database.
So what I basically have to do is a 'merge' from to my database so if data has been changed to a update if data is new do an insert. For some reason one my linq queries for this is giving an error.
My database:
My Code:
public void Insert(List<MEDSU1> medsu1s)
{
var keys = medsu1s.Select(y => new { SUPP = y.S1SUPP?.Trim() ?? null, COMP = y.S1COMP?.Trim() ??null }).ToList();
List<MEDSU1> medusFromSql = MediusDataContext.MEDSU1s.Where(y =>
keys.Any(z => z.SUPP == y.S1SUPP && z.COMP == y.S1COMP)).ToList();
var toUpdate = from medsu1org in MediusDataContext.MEDSU1s
join sqlkeys in medusFromSql
on new
{
aa = medsu1org.S1COMP,
bb = medsu1org.S1SUPP
}
equals
new
{
aa = sqlkeys.S1COMP,
bb = sqlkeys.S1SUPP
}
select new
{
sql = medsu1org,
obj = sqlkeys,
};
toUpdate.ToList().ForEach(y =>
{
y.obj.S1COMP= y.sql.S1COMP;
y.obj.S1SUPP = y.sql.S1SUPP;
y.obj.S1SUNA = y.sql.S1SUNA;
y.obj.S1BAAC = y.sql.S1BAAC;
y.obj.S1VATN = y.sql.S1VATN;
y.obj.S1COUN = y.sql.S1COUN;
y.obj.S1PREF = y.sql.S1PREF;
y.obj.S1TAXD = y.sql.S1TAXD;
y.obj.S1CURR = y.sql.S1CURR;
y.obj.S1TYPE = y.sql.S1TYPE;
y.obj.S1ACNR = y.sql.S1ACNR;
y.obj.S1ACNM = y.sql.S1ACNM;
y.obj.S1EINV = y.sql.S1EINV;
y.obj.S1DLAY = y.sql.S1DLAY;
y.obj.S1TERM = y.sql.S1TERM;
y.obj.S1PYEE = y.sql.S1PYEE;
});
var toInsert = medsu1s.Except(toUpdate.Select(y => y.obj)).ToList();
MediusDataContext.MEDSU1s.InsertAllOnSubmit(toInsert);
MediusDataContext.SubmitChanges();
}
The part of the code that is giving me the error is the:
var keys = medsu1s.Select(y => new { SUPP = y.S1SUPP?.Trim() ?? null, COMP = y.S1COMP?.Trim() ??null }).ToList();
List<MEDSU1> medusFromSql = MediusDataContext.MEDSU1s.Where(y =>
keys.Any(z => z.SUPP == y.S1SUPP && z.COMP == y.S1COMP)).ToList();
I think it has to do with me using .Any incorrectly and converting it to a List but i dont know to fix it. Can someone explain me what I'm doing wrong?
ERROR im getting : "Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."
Since you're dealing with a compound key you'll have to create a query that basically looks like
Select *
From MEDSU1s
Where (S1SUPP == #S1SUPP1 && S1COMP == #S1COMP1)
OR (S1SUPP == #S1SUPP2 && S1COMP == #S1COMP2)
OR ....
To do that with Linq you'd have to build the expression procedurally. Note that I'm assuming the columns are both strings, so change the type of them as needed. Also I have no good way of testing this out but hopefully this can get you started towards a solution.
var queryableData = MediusDataContext.MEDSU1s;
var table = Expression.Parameter(typeof(MEDSU1s), "x");
var suppCol = Expression.Property(table, typeof(string), "S1SUPP");
var compCol = Expression.Property(table, typeof(string), "S1COMP");
Expression condition = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
foreach (var x in keys)
{
var key1 = Expression.Equal(suppCol, Expression.Constant(x.SUPP));
var key2 = Expression.Equal(compCol, Expression.Constant(x.COMP));
var both = Expression.AndAlso(key1, key2);
condition = Expression.OrElse(condition, both);
}
var whereExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<MEDSU1s, bool>>(
condition,
new ParameterExpression[] { table }));
var medusFromSql = queryableData.Provider.CreateQuery<MEDSU1s>(whereExpression).ToList();

Linq where clause using filter object

I have a piece of Linq that queries an EntityFramework context in my web controller and returns the result, as follows:
[HttpGet]
public IActionResult GetRoutingRules()
{
var query = (from rr in _context.RoutingRules
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
return Ok(query);
}
I want a new method that will take a "filter" object, where I can narrow down the results of the above. My filter object looks like this:
public class RoutingSearchFilterDto
{
public int BrandId { get; set; }
public int? ServiceType { get; set; }
public long? OriginZoneId { get; set; }
public long? DestinationZoneId { get; set; }
public int? RuleRanking { get; set; }
public bool? IsRuleActive { get; set; }
}
The minimum info that needs to be set in this class is BrandId. All other properties are options in the filter.
I need to write a new controller method that will utilise this, something like:
[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
...
}
How do I linq query on properties that could potentially be null? Essentially, a dynamic query depending on the properties set in the filter object.
NOTE: I want this to affect the select statement that the EF runs, not just let EF get all the data, then filter the data set - the point of this is to make the db call more efficient.
Filter object might be sent where BrandId = 1, IsRuleActive = 1. Equally, it could be BrandId = 1, ServiceType = 3 (and therefore IsRuleActive is null so shouldn't be in the linq where clause).
I've tried this:
var param = (Expression.Parameter(typeof(RoutingRules), "rr"));
Expression combinedExpr = null;
if (filter.BrandId != null)
{
var exp = Expression.Equal(Expression.Property(param, "BrandId"), Expression.Constant(filter.BrandId));
combinedExpr = exp;
}
if (filter.DestinationZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "DestinationZoneId"), Expression.Constant(filter.DestinationZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.OriginZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "OriginZoneId"), Expression.Constant(filter.OriginZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.EshopServiceType != null)
{
var exp = Expression.Equal(Expression.Property(param, "EshopServiceType"), Expression.Constant(filter.EshopServiceType));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.IsRuleActive != null)
{
var exp = Expression.Equal(Expression.Property(param, "IsRuleActive"), Expression.Constant(filter.IsRuleActive, typeof(bool?)));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.RuleRanking != null)
{
var exp = Expression.Equal(Expression.Property(param, "RuleRanking"), Expression.Constant(filter.RuleRanking));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (combinedExpr == null)
combinedExpr = Expression.Default(typeof(bool));
var compiled = Expression.Lambda<Func<RoutingRules, bool>>(combinedExpr, param).Compile();
var results = (from rr in _context.RoutingRules.Where(compiled)
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
where rr.BrandId == 21
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
But the Where clause isn't applied to the generated Sql, it seems to pull back all records, then apply the where in memory, which isn't what I need.
Thanks in advance for any pointers!!
You can build an expression tree for this, but have you considered:
IQueryable<...> query = ...;
if (routingSearchFilter.ServiceType != null)
query = query.Where(e => e.ServiceType == routingSearchFilter.ServiceType);
if (...)
query = query.Where(....);
The EF engine is smart enough to combine the Where clauses (with AND of course).
Edit:
It wasn't clear if you wanted to filter on the joined result or only on the first table. In that case it would continue like
var result = (from rr in query
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join ...
select new RoutingRulesDto(rr) .... ).ToSometing();
But I'm a little wary about that RoutingRulesDto(rr) constructor parameter.
If you use the fluent API for LINQ, you can conditionally add Where clauses.
var query = _content.RoutingRules.Where(r => r.BrandId == filter.BrandId);
if (filter.OriginZoneId != null) {
query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
}
if (filter.EshopServiceType != null) {
query = query.Where(r => r.EshopServiceType == filter.EshopServiceType);
}
// etc...
var result = query.ToArray();
Just to have my final solution in black and white, here's what I had in the end:
[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
// Query to be build on the routing rules table.
IQueryable<RoutingRules> query = _context.RoutingRules;
// Populate the linked foreign key entities.
query.Include(x => x.Hub).Include(y => y.DestinationZone).Include(z => z.OriginZone);
// Build dynamic where statements.
if (filter.BrandId != null)
query = query.Where(r => r.BrandId == filter.BrandId);
if (filter.OriginZoneId != null)
query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
if (filter.DestinationZoneId != null)
query = query.Where(r => r.DestinationZoneId == filter.DestinationZoneId);
if (filter.IsRuleActive != null)
query = query.Where(r => r.IsRuleActive == filter.IsRuleActive);
if (filter.RuleRanking != null)
query = query.Where(r => r.RuleRanking == filter.RuleRanking);
// If you want to add paging:
query = query.Skip(filter.PageSize * filter.PageNumber).Take(filter.PageSize);
// Perform select on the table and map the results.
var result = query.Select(r => new RoutingRulesDto
{
RoutingRuleId = r.RoutingRuleId,
BrandId = r.BrandId,
LastMileCarrierCode = r.LastMileCarrierCode,
CashOnDelivery = r.CashOnDelivery,
CreationTime = r.CreationTime,
CurrencyCode = r.CurrencyCode,
CurrencyDescription = Enum.Parse(typeof(Enumerations.CurrencyCode), r.CurrencyCode),
DestinationZoneId = r.DestinationZoneId,
EddFromDay = r.EddFromDay,
EddToDay = r.EddToDay,
ServiceType = r.ServiceType,
ServiceTypeName = Enum.Parse(typeof(Enumerations.ServiceType), r.EshopServiceType),
IsPickUpAvailable = r.IsPickUpAvailable,
LastUpdateTime = r.LastUpdateTime,
LastUpdateUser = r.LastUpdateUser,
OriginZoneId = r.OriginZoneId,
RuleRanking = r.RuleRanking,
SignOnDelivery = r.SignOnDelivery,
TermsOfDelivery = r.TermsOfDelivery,
TermsOfDeliveryName = Enum.Parse(typeof(Enumerations.TermsOfDelivery), r.TermsOfDelivery),
ValueOfGoods = r.ValueOfGoods,
WeightLowerLimit = r.WeightLowerLimit,
WeightUpperLimit = r.WeightUpperLimit,
FirstMileCarrierCode = r.FirstMileCarrierCode,
HubId = r.HubId,
IsInsuranceAvailable = r.IsInsuranceAvailable,
IsRuleActive = r.IsRuleActive,
HubName = r.Hub.HubName,
DestinationZoneName = r.DestinationZone.ZoneName,
OriginZoneName = r.OriginZone.ZoneName,
});
// The SQL produced includes the joins and where clauses as well as only
// selecting the column names that are required in the flattened return object.
return Ok(result);
}
Thanks for the help guys!

How to solve error in LINQ: Data is null or empty

I have the following code. I want to check if the result is null with an if condition but it always shows an error. How to solve this?
string StrRefNo = Request.QueryString["ref"];
string[] SMSid = StrRefNo.Split('$');
DownloadsDbEntities db = new DownloadsDbEntities();
var data = (from d in db.SMSLink_Expiry
where d.RefNo == SMSid[0]
&& d.SMSLink.ID == Convert.ToInt32(SMSid[1])
select d).ToList();
if (data.Count > 0)
{
string ss = "yes";
}
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities.
Since the expression can not be translated into SQL, pull it out of the statement
string SMSId0 = SMSid[0];
int SMSId1 = Convert.ToInt32(SMSid[1]);
var data = (from d in db.SMSLink_Expiry
where d.RefNo == SMSId0
&& d.SMSLink.ID == SMSId1
select d).ToList();
This should fix your problem:
string StrRefNo = Request.QueryString["ref"];
string[] SMSid = StrRefNo.Split('$');
var ref = SMSid[0];
var smsLinkId = Convert.ToInt32(SMSid[1]);
DownloadsDbEntities db = new DownloadsDbEntities();
var data = (from d in db.SMSLink_Expiry
where d.RefNo == ref
&& d.SMSLink.ID == smsLinkId
select d).ToList();
if (data.Count > 0)
{
string ss="yes";
}
There are many things Linq does not support, since its translating the query to SQL internally.
For reference: The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities
You can try this :
string[] SMSid = StrRefNo.Split('$');
var refno=SMSid[0];
var id=Convert.ToInt32(SMSid[1];
DownloadsDbEntities db = new DownloadsDbEntities();
var data = (from d in db.SMSLink_Expiry
where d.RefNo == refno
&& d.SMSLink.ID == id)
select d).ToList();

Linq to SQL - Ignore search parameters that are null or zero

I have a search form where the user can enter one to many parameters (Data, Status, Type, ID, Summary, Description) and leave the rest blank.
Here's my Linq to SQL code for my basic search. Is there a way to check each parameter within the Linq for zero, null or empty string?
List<RequestStatusModel> objRequestStatus = new List<RequestStatusModel>();
var query = from r in SimCareDB.Requests
where r.CustomerID == 31
select (new RequestStatusModel
{
RequestID = r.RequestID,
RequestTitle = r.RequestTitle,
DateAdded = r.DateAdded.ToString(),
DateChanged = r.DateChanged.ToString(),
RequestStatusID = r.StatusID
});
Thank you!
If it doesn't have to be in your linq statement you could just do it with classic if statements.
List<RequestStatusModel> objRequestStatus = new List<RequestStatusModel>();
var query = from r in SimCareDB.Requests
where r.CustomerID == 31
select (new RequestStatusModel
{
//...
});
if(data != null) //Replace with additional checks, if neccessary
{
query = query.where(x=> ...);
}
if(status != null)
{
query = query.where(x => ...)
}
If you want to only filter if certain criteria is passed, you should do something like this
var objRequestStatus = new List<RequestStatusModel>();
var query = from r in SimCareDB.Requests
where r.CustomerID == 31
if (String.IsNullOrEmpty(r.RequestID))
objRequestStatus = objRequestStatus.Where(x => x.RequestID == r.RequestID);
if (String.IsNullOrEmpty(r.RequestTitle))
objRequestStatus = objRequestStatus.Where(x => x.RequestTitle == r.RequestTitle);
//you other filters here
This sets up the expression to what you want based on which requests are passed
If you want to avoid all those ifs, you could do
List<RequestStatusModel> objRequestStatus = new List<RequestStatusModel>();
var query = from r in SimCareDB.Requests
where (r.CustomerID == 31) &&
(!String.IsNullOrEmpty(id) ? r.RequestID == id : true) &&
(!String.IsNullOrEmpty(status) ? r.StatusID == status : true)
/* And so on */
select (new RequestStatusModel
{
RequestID = r.RequestID,
RequestTitle = r.RequestTitle,
DateAdded = r.DateAdded.ToString(),
DateChanged = r.DateChanged.ToString(),
RequestStatusID = r.StatusID
});

Categories

Resources