Cannot convert Linq to SQL - c#

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();

Related

LinqToDb: Rank is server-side method

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();

Multiple condition using Lambda Expression in C#

The following code works for me if I need to filter a generic query for a single parameter, like Status=1.
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query)
{
var propertyName = "Status";
var param_1 = "1";
//var param_2 = 2;
//var param_3 = "DE";
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
var converter = TypeDescriptor.GetConverter(propertyExp.Type);
var result = converter.ConvertFrom(param_1);
var value = Expression.Constant(result);
var equalBinaryExp = Expression.Equal(propertyExp, value);
query = query.Where(Expression.Lambda<Func<T, bool>>(equalBinaryExp, parameterExp));
return query;
}
This is like filtering a list with a value.
var list = new List<Employee> {
new Employee { Id = 1, Name = "Phil", Status=1, Country="DE" } ,
new Employee { Id = 2, Name = "Kristina", Status=2, Country="DE" },
new Employee { Id = 3, Name = "Mia", Status=1, Country="US" }
};
list = list.Where(x => (x.Status == 1)).ToList();
But how should I modify the method FilterBy, so that it will work for filtering multiple values? Like (Status=1 or Status=2) and Country="DE",
list = list.Where(x => (x.Status == 1 || x.Status == 2) && x.Country == "DE").ToList();
Thanks for your time.
The trick when writing lambdas by hand is to write them how you would in regular C#, and decompile them, perhaps using sharplab like this. If you look on the right, you can see that it is doing something like:
Expression.AndAlso(
Expression.OrElse(
Expression.Equal(idPropExp, Expression.Constant(1)),
Expression.Equal(idPropExp, Expression.Constant(2))
),
Expression.Equal(countryPropExp, Expression.Constant("DE"))
)

How do you reuse mapping functions on Nested entities in Entity Framework?

I have seen multiple questions that are similar to this one but I think my case is slightly different. I'm using EF6 to query the database and I'm using data projection for better queries.
Given that performance is very important on this project I have to make sure to just read the actual fields that I will use so I have very similar queries that are different for just a few fields as I have done this I have noticed repetition of the code so I'm been thinking on how to reuse code this is currently what I Have:
public static IEnumerable<FundWithReturns> GetSimpleFunds(this DbSet<Fund> funds, IEnumerable<int> fundsId)
{
IQueryable<Fund> query = GetFundsQuery(funds, fundsId);
var results = query
.Select(f => new FundWithReturns
{
Category = f.Category,
ExpenseRatio = f.ExpenseRatio,
FundId = f.FundId,
Name = f.Name,
LatestPrice = f.LatestPrice,
DailyReturns = f.FundDailyReturns
.Where(dr => dr.AdjustedValue != null)
.OrderByDescending(dr => dr.CloseDate)
.Select(dr => new DailyReturnPrice
{
CloseDate = dr.CloseDate,
Value = dr.AdjustedValue.Value,
}),
Returns = f.Returns.Select(r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
}).FirstOrDefault()
})
.ToList();
foreach (var result in results)
{
result.DailyReturns = result.DailyReturns.ConvertClosingPricesToDailyReturns();
}
return results;
}
public static IEnumerable<FundListVm> GetFundListVm(this DbSet<Fund> funds, string type)
{
return funds
.Where(f => f.StatusCode == MetisDataObjectStatusCodes.ACTIVE
&& f.Type == type)
.Select(f => new FundListVm
{
Category = f.Category,
Name = f.Name,
Symbol = f.Symbol,
Yield = f.Yield,
ExpenseRatio = f.ExpenseRatio,
LatestDate = f.LatestDate,
Returns = f.Returns.Select(r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
}).FirstOrDefault()
}).OrderBy(f=>f.Symbol).Take(30).ToList();
}
I'm trying to reuse the part where I map the f.Returns so I tried created a Func<> like the following:
private static Func<Return, ReturnValues> MapToReturnValues = r => new ReturnValues
{
Daily = r.AdjDaily,
FiveYear = r.AdjFiveYear,
MTD = r.AdjMTD,
OneYear = r.AdjOneYear,
QTD = r.AdjQTD,
SixMonth = r.AdjSixMonth,
ThreeYear = r.AdjThreeYear,
YTD = r.AdjYTD
};
and then use like this:
public static IEnumerable<FundListVm> GetFundListVm(this DbSet<Fund> funds, string type)
{
return funds
.Where(f => f.StatusCode == MetisDataObjectStatusCodes.ACTIVE
&& f.Type == type)
.Select(f => new FundListVm
{
Category = f.Category,
Name = f.Name,
Symbol = f.Symbol,
Yield = f.Yield,
ExpenseRatio = f.ExpenseRatio,
LatestDate = f.LatestDate,
Returns = f.Returns.Select(MapToReturnValues).FirstOrDefault()
}).OrderBy(f=>f.Symbol).Take(30).ToList();
}
The compiler is ok with it but at runtime, it crashes and says: Internal .NET Framework Data Provider error 1025
I tried to convert the Func into Expression like I read on some questions and then using compile() but It didn't work using AsEnumerable is also not an option because It will query all the fields first which is what I want to avoid.
Am I trying something not possible?
Thank you for your time.
It definitely needs to be Expression<Func<...>>. But instead of using Compile() method (not supported), you can resolve the compile time error using the AsQueryable() method which is perfectly supported (in EF6, the trick doesn't work in current EF Core).
Given the modified definition
private static Expression<Func<Return, ReturnValues>> MapToReturnValues =
r => new ReturnValues { ... };
the sample usage would be
Returns = f.Returns.AsQueryable().Select(MapToReturnValues).FirstOrDefault()

If Else in linq with anonymous object

How to do an If Else conditional in a LINQ query?
cashierdata.useDenominations is boolean type, i'm doing the casting to the same object .
Something like
IQueryable<CashierBalance> icashierBalance = _cashierDataManagement.GetIQueryableCashierBalance();
var currencies = icashierBalance.Select(a => new
{
Id = a.Currency.Id,
Name = a.Currency.Name,
Simbol = a.Currency.Symbol,
ShorName = a.Currency.ShortName,
RoundingUp = a.Currency.RoundingUp,
RoundingDown = a.Currency.RoundingDown,
DenominationMin = a.Currency.DenominationMin,
Denominations = cashierdata.useDenominations ? (Denomination) a.Currency.Denominations.Select(q => q )
: (Denomination) null
});
The response from the api
Unable to cast the type 'System.Collections.Generic.IEnumerable`1[[Tellers.Denomination, DynamicFieldsDiagramLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' to type 'Tellers.Denomination'. LINQ to Entities only supports casting EDM primitive or enumeration types.
Without casting
IQueryable<CashierBalance> icashierBalance = _cashierDataManagement.GetIQueryableCashierBalance();
var currencies = icashierBalance.Select(a => new
{
Id = a.Currency.Id,
Name = a.Currency.Name,
Simbol = a.Currency.Symbol,
ShorName = a.Currency.ShortName,
RoundingUp = a.Currency.RoundingUp,
RoundingDown = a.Currency.RoundingDown,
DenominationMin = a.Currency.DenominationMin,
Denominations = cashierdata.useDenominations ? a.Currency.Denominations.Select(q => q )
: null
});
the exception
The nested query is not supported. Operation1='Case' Operation2='Collect'
The problem is that your IQueryable is attempting to parse your expression and convert it into an SQL expression. This is beyond the capabilities of Entity Framework (specifically, it is unable to assign an enumerable of objects to Denominations).
In this case, you just want to fetch the data from the database, then perform your conversion in the .NET client.
To achieve this, convert your IQueryable to LINQ2Objects by invoking AsEnumerable.
IQueryable<CashierBalance> icashierBalance = _cashierDataManagement.GetIQueryableCashierBalance();
var currencies = icashierBalance
.AsEnumerable()
.Select(a => new
{
Id = a.Currency.Id,
Name = a.Currency.Name,
Simbol = a.Currency.Symbol,
ShorName = a.Currency.ShortName,
RoundingUp = a.Currency.RoundingUp,
RoundingDown = a.Currency.RoundingDown,
DenominationMin = a.Currency.DenominationMin,
Denominations = cashierdata.useDenominations ? a.Currency.Denominations.Select(q => q ) : null
});
Try this:
IQueryable<CashierBalance> icashierBalance = _cashierDataManagement.GetIQueryableCashierBalance();
var currencies = icashierBalance.Select(a => new
{
Id = a.Currency.Id,
Name = a.Currency.Name,
Simbol = a.Currency.Symbol,
ShorName = a.Currency.ShortName,
RoundingUp = a.Currency.RoundingUp,
RoundingDown = a.Currency.RoundingDown,
DenominationMin = a.Currency.DenominationMin,
Denominations = cashierdata.useDenominations ? a.Currency.Denominations : null
});

Check for missing elements while using LINQ to XML

I am trying get data from the xml. Below is the code which
gets data from the XDocument and return list<t>.
However, p.Element("Sponsor") can sometimes be null. How can I check for the null values
var atClauseList = doc.Descendants(CLAUSE_GROUP_TAG).Descendants(AT_CLAUSE_TAG).Select(p => new AtClause()
{
ClauseNumber = (string)p.Element("Number"),
Sponsors = p.Element("Sponsor").Elements(SPONSOR_TAG).Select(y => y.Value)
.ToList(),
Page = p.Element("Sponsor").Element("aItem").Element("AmendText").Element("Page").ElementValueNull(),
Line = p.Element("Sponsor").Element("aItem").Element("AmendText").Element("Line").ElementValueNull(),
LineText = p.Element("Sponsor").Element("aItem").Element("AmendText").Nodes().OfType<XText>().FirstOrDefault().XTextValueNull(),
ItalicText = p.Element("Sponsor").Element("aItem").Element("AmendText").Element("Italic").ElementValueNull(),
ParaList = p.Element("Sponsor").Element("aItem").Element("AmendText").Elements("Para").Select(L => new Para
{
ParaText = (string)L,
Number = ((System.Xml.Linq.XElement)(L)).AttributeValueNull("Number"),
Quote = ((System.Xml.Linq.XElement)(L)).AttributeValueNull("Quote"),
}
).ToList()
}).ToList();
move your code out of an object initializer, and add some logic to it:
var atClauseList = new List<AtClause>();
foreach(var item in doc.Descendants(CLAUSE_GROUP_TAG).Descendants(AT_CLAUSE_TAG))
{
var atClause = new AtClause();
atClause.ClauseNumber = (string)item.Element("Number");
var sponsor = item.Element("Sponsor");
if (sponsor != null)
{
atClause.Sponsors = sponsor.Elements(SPONSOR_TAG).Select(y => y.Value).ToList();
atClause.Page = sponsor.Element("aItem").Element("AmendText").Element("Page").ElementValueNull();
atClause.Line = sponsor.Element("aItem").Element("AmendText").Element("Line").ElementValueNull();
atClause.LineText = sponsor.Element("aItem").Element("AmendText").Nodes().OfType<XText>().FirstOrDefault().XTextValueNull();
atClause.ItalicText = sponsor.Element("aItem").Element("AmendText").Element("Italic").ElementValueNull();
atClause.ParaList = sponsor.Element("aItem").Element("AmendText").Elements("Para").Select(L => new Para
{
ParaText = (string)L,
Number = ((System.Xml.Linq.XElement)(L)).AttributeValueNull("Number"),
Quote = ((System.Xml.Linq.XElement)(L)).AttributeValueNull("Quote"),
}).ToList();
atClauseList.Add(atClause);
}
You can use sequences rather than leaving the IEnumerable immediately:
var value = (string)p.Elements("Sponsor")
.Elements("aItem")
.Elements("AmendText")
.Elements("Page")
.SingleOrDefault()

Categories

Resources