Hi this is my first question so apologies if it is really basic - I am very new to programming!!!
Using c# in MVC I am trying to select object which has a Date property from entitymodel context. This date then selects the relevant Weight object and so on to get my list of "Set" objects.
The code works and does what I want but would like some general guidance on how to make this code more concise. Here is the code:
public ActionResult showDiary(string datein)
{
LocalTestEntities1 dblists = new LocalTestEntities1();
DateTime date = Convert.ToDateTime(datein);
IEnumerable<ExerciseDiary> diary = from o in dblists.ExerciseDiaries where o.Date == date select o;
var mydiary = diary.ToList();
ExerciseDiary thediary = mydiary[0];
IQueryable<Weight> weights = from o in dblists.Weights where o.DiaryID == thediary.ID select o;
var selectedWeight = weights.ToList();
Weight weight = selectedWeight[0];
IEnumerable<Set> sets = from x in dblists.Sets where x.WeightId == weight.WeightID select x;
return View(sets);
}
It seems that I am taking too many steps here. I know that I am only returning one object to diary. Is there a way to get this object from dblists without sending to an IEnumerable?
Using the First() method will make things a little more concise:
public ActionResult showDiary(string datein)
{
using (LocalTestEntities1 dblists = new LocalTestEntities1())
{
DateTime date = Convert.ToDateTime(datein);
var thediary = (from o in dblists.ExerciseDiaries
where o.Date == date
select o).First();
var weight = (from o in dblists.Weights
where o.DiaryID == thediary.ID
select o).First();
var sets = (from x in dblists.Sets
where x.WeightId == weight.WeightID
select x).ToList();
}
return View(sets);
}
You should also wrap your LINQ to Entities data access in a using block so it's properly disposed of.
There's always many ways to do things, but... I think the easiest way would be to use First() since you are always just grabbing the first result in a list.
Another way to make it a little cleaner is to put your LINQ statements on multiple lines like I did for sets.
You can also use var, which some people like and others don't to have the compiler infer the type. I did this with sets below. I feel it cleans up the code a bit when you have large declarations with IEnumerable and generics.
public ActionResult showDiary(string datein)
{
LocalTestEntities1 dblists = new LocalTestEntities1();
DateTime date = Convert.ToDateTime(datein);
ExerciseDiary thediary = dblists.ExerciseDiaries.First(o => o.Date == date);
Weight weight = dblists.Weights.First(o.DiaryID == thediary.ID);
var sets = from x in dblists.Sets
where x.WeightId == weight.WeightID
select x;
return View(sets);
}
IMO this is easier to read than what you had in your answer above.
Be careful using First() because it will throw an exception if there are no records.
public ActionResult showDiary(string datein)
{
using( var dblists = new LocalTestEntities1())
{
var date = Convert.ToDateTime(datein);
var thediary = dblists.ExerciseDiaries.First(o => o.Date == date);
var weight = dblists.Weights.First(o => o.DiaryID ==thediary.ID);
var sets = dblists.Sets.Where(x => x.WeightId == weight.WeightID).AsEnumerable();
return View(sets);
}
}
Warning: If it's possible the data wont always be there. Use FirstOrDefault instead and check for null values.
Related
I have som strings like "1","2","3","10" and etc and when use orderby sorted list is "1","10","2","3". I want to sort them as number like 1,2,3,...,10. I use below code to sort the list.
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName }).OrderBy(c => c.id,
new SemiNumericComparer());
if (model.Any())
{
CacheManager.cache.GetOrAdd<List<ReturnData>>(key, () =>
model.ToList<ReturnData>());
return model.ToList<ReturnData>();
}
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
{
if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
}
if (IsNumeric(s1) && !IsNumeric(s2))
return -1;
if (!IsNumeric(s1) && IsNumeric(s2))
return 1;
return string.Compare(s1, s2, true);
}
public static bool IsNumeric(object value)
{
try
{
int i = Convert.ToInt32(value.ToString());
return true;
}
catch (FormatException)
{
return false;
}
}
}
when I run the code I get this error :
LINQ to Entities does not recognize the method 'System.Linq.IOrderedQueryable`1[Salary.Classes.ReturnData] OrderBy[ReturnData,String](System.Linq.IQueryable`1[Salary.Classes.ReturnData], System.Linq.Expressions.Expression`1[System.Func`2[Salary.Classes.ReturnData,System.String]], System.Collections.Generic.IComparer`1[System.String])' method, and this method cannot be translated into a store expression.
It's a legacy database and I can't change any data type because may raise error on other applications.
You have two problems here:
You are storing numbers as strings in your database and
Youre trying to execute C# code on Sql Server
The exception you are receiving is due to the fact that the compiler cannot translate the comparison logic from SemiNumericComparer class into a sql query.
In order to achieve the desired result you could:
a) Load all data in memory and perform the comparison using SemiNumericComparer in memory by iterating through the selected results and ordering them after that like this:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.ToList() // this will load data into memory
.OrderBy(c => c.id, new SemiNumericComparer());
This, however is not a good approach because it will add a lot of useless memory consumption if the dataset is quite small and will crash your application if your dataset is larger than the available memory at a given time.
Edit As pointed out by #gsubiran this approach is not valid.
b) Convert your strings into numbers on Sql Server using SqlFunctions and order them as numbers using the ordering provided by Sql Server:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new ReturnData { id = c.BaseCode, name = c.FAName })
.OrderBy(c => SqlFunctions.IsNumeric(c.id));
If you want a numerical order you have to sort numberical data types:
string[] str = {"1", "10", "2","011"};
List<string> ordered = str.OrderBy(x => int.Parse(x)).ToList();
Try something like this:
var model = (from c in General.db.GlbTbComboBases
where c.ClassCode.Equals(classCode)
select new { Id = Convert.ToInt32(c.BaseCode), Name = c.FAName })
.OrderBy(c => c.Id)
.Select(x => new ReturnData { id = x.Id, name = x.Name });
Simply added an anonymous type to sort and then converted to the required type. Of course it takes more memory.
There is no a built in way to convert string to int on linq to entities.
SqlFunctions class has many useful functions but no one of them have support to convert/parse/cast string to int.
You could use your own custom sql function and then add it to your EF model and use it as you need. I tested it with table value function, I don't know if it also work with scalar value functions.
CREATE FUNCTION [dbo].[StringToInt](
#strInt AS VARCHAR(30)
)
RETURNS TABLE WITH SCHEMABINDING
RETURN
SELECT TRY_CAST (#strInt AS INT) [IntValue];
Then you add it to your EF model (YourModel.context.cs)
[DbFunction("YourContext", "StringToInt")]
public virtual IQueryable<Nullable<int>> StringToInt(string strInt)
{
var strIntParameter = strInt != null ?
new ObjectParameter("strInt", strInt) :
new ObjectParameter("strInt", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Nullable<int>>("[YourContext].[StringToInt](#strInt)", strIntParameter);
}
Finally you just could use it in a sorting expression in this way:
var qry = context.SomeEntityYouHave
.Where(x => x.Category == "Numbers")
.OrderBy(
x => ctx.StringToInt(x.StringPropertyContainingNumbersToSort)
.FirstOrDefault()//Don't forget to call FirstOrDefault
);
With this approach you are sorting data on SQL side avoiding the need to do an early call to .ToList() and then sort all the data on application memory.
Of course is a better approach to have int values stored as int instead of strings but some times for different reasons it couldn't be an option.
My project is using MVC 4 C# LINQ to SQL.
For some reason the method used to get data for one of my properties is giving a "has no supported translation to SQL" error. The method to fetch this data is nearly identical to the method of another property in the same query except the one that works grabs a string where the one that doesn't gets a decimal.
Exact error code:
Method 'System.Decimal GetModelDifficulty(System.String)' has no supported translation to SQL.
I've tried numerous variations on the below code but I always get the same error as above:
public List<ProductionSchedule> GetBaseProductionSchedule(DateTime selectedDate)
{
var spaList = (from x in db.WO010032s
join y in db.MOP1042s on x.MANUFACTUREORDER_I equals y.MANUFACTUREORDER_I into x_y
where x.STRTDATE == selectedDate && (x.MANUFACTUREORDERST_I == 2 || x.MANUFACTUREORDERST_I == 3)
from y in x_y.DefaultIfEmpty()
select new ProductionSchedule()
{
MO = x.MANUFACTUREORDER_I,
BOMNAME = x.BOMNAME_I,
SpaModel = x.ITEMNMBR,
MoldType = GetMoldType(x.ITEMNMBR.Trim()),
SerialNumber = y.SERLNMBR,
Difficulty = GetModelDifficulty(x.ITEMNMBR.Trim())
}).OrderByDescending(x => x.Difficulty).ToList();
return spaList;
}
public string GetMoldType(string model)
{
return db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.MoldType).FirstOrDefault();
}
public decimal GetModelDifficulty(string model)
{
return (decimal)db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.Difficulty).FirstOrDefault();
}
Well I've tweaked the code around enough times to where I've stumbled on a variation that works:
public List<ProductionSchedule> GetBaseProductionSchedule(DateTime selectedDate)
{
var spaList = (from x in db.WO010032s
join y in db.MOP1042s on x.MANUFACTUREORDER_I equals y.MANUFACTUREORDER_I into x_y
where x.STRTDATE == selectedDate && (x.MANUFACTUREORDERST_I == 2 || x.MANUFACTUREORDERST_I == 3)
from y in x_y.DefaultIfEmpty()
select new ProductionSchedule()
{
MO = x.MANUFACTUREORDER_I,
BOMNAME = x.BOMNAME_I,
SpaModel = x.ITEMNMBR,
MoldType = GetMoldType(x.ITEMNMBR.Trim()),
SerialNumber = y.SERLNMBR,
Difficulty = GetModelDifficulty(x.ITEMNMBR)
}).ToList();
return spaList.OrderByDescending(x => x.Difficulty).ToList();
}
public string GetMoldType(string model)
{
return db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.MoldType).FirstOrDefault();
}
public decimal GetModelDifficulty(string model)
{
decimal difficulty = (String.IsNullOrEmpty(model)) ? 0M : Convert.ToDecimal(db.SkuModelDatas.Where(x => x.Value == model.Trim()).Select(x => x.Difficulty).FirstOrDefault());
return difficulty;
}
Why it worked when trapping for null string for x.ITEMNMBR (model parameter) in one method and not the other and needing to OrderByDescending outside of the main LINQ query, I have no idea.
Thanks for all the suggestions and help with this.
The problem is your query is calling code that LINQ cannot translate into SQL.
First try this, it may help. There may be a problem with your (decimal) cast. Modify your method GetModelDifficulty to the following:
public decimal GetModelDifficulty(string model)
{
return Convert.ToDecimal(db.SkuModelDatas.Where(x => x.Value == model).Select(x => x.Difficulty).FirstOrDefault());
}
If that doesn't work I'm afraid you'll have to break your query down further to narrow down the issue. Use the documentation provided here: Standard Query Operator Translation (LINQ to SQL) to be sure that any extension methods you are using have supported translations.
If you run into a piece of code that cannot be translated, you may need to declare a separate variable with the necessary value already stored inside of it, that you can then use inside of your query.
I think it's because your call to FirstOrDefault() can return a null value. When you assign to a typed object you can use ? operator to signify that it can be null:
decimal? myDec = <some code returning decimal value or null>
Then you can check if myDec is null or not by:
myDec.HasValue
I'm using LINQ to Entities and I want to know how do I translate the following query to lambda expression using extension methods.
public _Deposito RegresaDepositosBancarios(int id)
{
return (from d in context.depositos_bancarios
where d.IDDeposito == id
select new _Deposito
{
idDeposito = d.IDDeposito,
cantidad = d.Monto,
fecha = d.FechaDeposito,
aplicado = d.Aplicado
}).Single();
}
Notice that I'm returning a _Deposito type, how do I achieve this using extension methods?
I need something like the following:
public Persona RegresaPersonaPorNombres(string nombres, string apellidoP, string apellidoM)
{
var p = context.personas.Where(x => x.Nombres == nombres &&
x.ApellidoP == apellidoP &&
x.ApellidoM == apellidoM).FirstOrDefault();
return p;
}
I don't want to return an entity type but a custom type instead
This is how this would be written with extension methods, but you really should not need to worry as they are both the same thing.
return context.depositos_bancarios
.Where(d=>d.IDDeposito == id)
.Select(d=>new _Deposito
{
idDeposito = d.IDDeposito,
cantidad = d.Monto,
fecha = d.FechaDeposito,
aplicado = d.Aplicado
})
.Single();
An interesting side note: I could have used a d=> in the Where and then an e=> in the Select. Whereas, the d propogates down throughout the phrase. The only way to reset it would be to use a let phrase. This has nothing to do with the direct question, but I just thought it interesting and wanted to point it out :)
I am still coming to terms with LINQ and I am trying to refactor the following foreach loop into it's LINQ equivalent. Here's the foreach loop that I am trying to convert;
var NetTotal = 0M;
foreach (var cheque in ListOfCheques)
{
var exchangeRate = (from exc in cheque.ExchangeRates
where exc.Type == EnumExchangeRate.ForCheque
select exc).FirstOrDefault();
NetTotal = exchangeRate != null ? NetTotal + cheque.NetAmount * exchangeRate.Rate : NetTotal + cheque.NetAmount;
}
return NetTotal ;
and the LINQ code that I've come up with;
var NetTotal = (from cheque in ListOfCheques
join exc in ListOfCheques.SelectMany(b => b.ExchangeRates) on cheque.ID equals exrate.Cheque.ID into chequeWithRate
where income.ExchangeRates.Select(x => x.Type).Equals(EnumExchangeRate.ForCheque)
from ur in chequeWithRate.DefaultIfEmpty()
select ur).Sum(x => x.Cheque.NetAmount * x.Rate);
return NetTotal;
The important points that I am struggling with;
It's possible that the "ExchangeRates" list within the Cheque class does not exist, i.e. it does not need an exchange rate.
If there is no exchange rate found it should default to 1. How can I set that... I was hoping to set it within DefaultIfEmpty(1).
Any help is greatly appreciated.
Like any refactoring, just chip away piece by piece. Firstly, rather than foreach just use a Sum() like so.
return ListOfCheques.Sum(c =>
{
var exchangeRate = (from exc in c.ExchangeRates
where exc.Type == EnumExchangeRate.ForCheque
select exc).FirstOrDefault();
return c.NetAmount * (exchangeRate ?? new ExchangeRate(){ Rate = 1 }).Rate;
});
(it'd be nice if the default value for the ExchangeRate.Rate property was 1)
I'd rewrite the exchangeRate function to a simple format. Are you sure you want FirstOrDefault and not SingleOrDefault?
var exchangeRate = c.ExchangeRates.
FirstOrDefault(ex => ex.Type == EnumExchangeRate.ForCheque);
and then this can be swapped into the first statement, leaving the resulting final product below.
A one liner if you want it to be!
return ListOfCheques.Sum(c => c.NetAmount *
(c.ExchangeRates.FirstOrDefault(ex => ex.Type == EnumExchangeRate.ForCheque)
?? new ExchangeRate() { Rate = 1 }).Rate);
edit
Clarification on the ?? new ExchangeRate()
Rather than doing the != null ? (amount * rate) : (rate) I prefer to coalesce the ExchangeRate object with a new such object with Rate = 1. I think this provides a smoother and cleaner piece of code. I'd strongly suggest you make the default Rate be 1.0, and then you can simply coalesce with a new ExchangeRate(), without needing to set the Rate property.
To set a default value for the Rate in a new ExchangeRate object, just put the initializer inside the constructor
class ExchangeRate
{
public ExchangeRate()
{
this.Rate = 1.0;
}
// other stuff
}
How about this?
var query =
from cheque in ListOfCheques
let rate =
cheque.ExchangeRates
.Where(exc => exc.Type == EnumExchangeRate.ForCheque)
.Select(exc => exc.Rate)
.DefaultIfEmpty(1.0M)
.First()
select rate * cheque.NetAmount;
var NetTotal = query.Sum();
Your LINQ query example given in your question has "extra" stuff that you didn't explain so I've only included the stuff from your foreach loop.
Do you need this?
var query = from cheque in ListOfCheques
let excRates = cheque.ExchangeRates ?? Enumerable.Empty()
let rate = excRates.Where(x => x.Type == Something).Select(x => x.Rate).FirstOrDefault() ?? 1
select cheque.NetAmount * rate;
var netTotal = query.Sum();
If Rate is nulllable, you can either adept that in the let statement by making it nullable (e.g. Select(x => new int?(x.Rate)) or remove ?? 1 and adept it in your select. which will make:
var query = from cheque in ListOfCheques
let excRates = cheque.ExchangeRates ?? Enumerable.Empty()
let rate = excRates.Where(x => x.Type == Something).Select(x => x.Rate).FirstOrDefault()
select cheque.NetAmount * (rate != 0 ? rate : 1);
I have a linq query from two database, however, each time the program stops at the query point. I don't know how to debug linq using VS. Can someone help me figure it out what's wrong here? Thank you.
public List<Promotion> GetBroder(string source)
{
string _connString = ConfigurationManager.AppSettings["DB1"];
PromotionDataContext dc = new PromotionDataContext(_connString);
string connString = ConfigurationManager.AppSettings["DB2"];
ReachDirectDataContext RDdc = new ReachDirectDataContext(connString);
return (from b in RDdc.BrokerNos
from p in dc.Promotions
where p.Source == source && p.Broker == b.BrokerNo1
select new Promotion() {Code=p.Code,BrokerName=b.Name}).ToList<Promotion>();
}
Your linq statement looks fine. To aid in debugging, I find it helpful to assign the linq query to a local variable, then return the local variable. You can then set a breakpoint on the return statement, and when the debugger stops at the breakpoint, you can inspect the query local variable to see what's in it, interactively. You can use the Locals window in VS, or the Immediate Window to surf around inside your app's variables and see what's going on.
In particular, double check that the inputs into your linq query are actually providing data. Verify that RDdc.Brokernos is non-empty, and dc.Promotions, etc. If these are empty, the result will be empty. Track your bug "upstream".
Minor point: You don't need to specify the type parameter on the .ToList() call in the select. The compiler will infer the type automagically.
You can use the following to display the generated SQL for the Linq statement.
ReachDirectDataContext RDdc = new ReachDirectDataContext(connString);
RDdc.Log = Console.Out;
return (from b in RDdc.BrokerNos
from p in dc.Promotions
where p.Source == source && p.Broker == b.BrokerNo1
select new Promotion() {Code=p.Code,BrokerName=b.Name}).ToList<Promotion>();
You can try the following to separate out the queries.
var promotions = from p in dc.Promotions
where p.Source == source
select p;
var brokers = from o in promotions
join b in RDdc.BrokerNos on o.Broker equals b.BrokerNo1
select new Promotion
{
Code = o.Code,
BrokerName = b.Name
};
return brokers.ToList();
The double from looks suspicious to me.
(This is not the equivalent to SQL's JOIN statement, if that is what you were aiming for.)
If you can combine the contexts:
Try creating relationship between BrokerNos and Promotions in edmx and using navigation property in query.
For example:
var result = dc.Promotions.Where(p => p.Source == source).
Select(p => new Promotion() {
Code = p.Code,
BrokerName = p.Broker.Name, // use new navigation property here
});
If not (intersection will be done in memory, not on DB!!!:
var result1 = dc.Promotions.Where(p => p.Source == source).
Select(p => new Promotion() {
Code = p.Code,
BrokerId = p.BrokerId, // add id property for intermediate results
}).ToList();
var result2 = RDdc.Brokers.ToList();
var finalResult = result1.Where(p => result2.Contains(b => b.BrokerId == p.BrokerId)).Select(p => new Promotion{
Code = p.Code,
BrokerName = result2.Single(b => b.BrokerId == p.BrokerId).Name,
});