I have this LINQ query which queries SQL Express database through Entity Framework but doesn't return unique rows when written this way in Visual Studio:
var q = from s in _db.SD
where s.ID == ID && s.Time >= startDate && s.Time <= endDate
select s
It returns correct number of rows but each row has same data as first one. However, when I tested the same query in LinqPad the result returned is fine i.e. correct number of rows and unique data in each row.
Secondly, if I try to change the statment to this:
var q = from s in _db.SD
where s.ID == ID && s.Time >= startDate && s.Time <= endDate
select s.Data
Then I got correct number of rows and unique value in each row.
Can someone please help me find out the problem with code?
Thanks in advance.
EDIT:
Here's full function:
public List<SD> GetHistory(int ID, DateTime startDate, DateTime endDate)
{
var q = (from s in _db.SD
where s.ID == ID &&
s.Time >= startDate && s.Time <= endDate
select s).ToList();
return q;
}
As you are getting correct distinct data using select s.Data, then you can write a Comparer class implementing IEqualityComparer<> inside the SD class as follows:
public class SD
{
public Int16 ID { get; set; }
public Byte MT { get; set; }
public Byte Data { get; set; }
public Byte SS { get; set; }
public DateTime Time { get; set; }
public class Comparer : IEqualityComparer<SD>
{
public bool Equals(SD x, SD y)
{
return x.Data == y.Data; // look...here I m using 'Data' field for distinction
}
public int GetHashCode(SD obj)
{
unchecked // overflow is fine
{
int hash = 17;
hash = hash * 23 + obj.Data.GetHashCode();
return hash;
}
}
}
}
Then you can write the GetHistory() method passing the Comparer in the Distinct() method as follows:
public List<SD> GetHistory(int ID, DateTime startDate, DateTime endDate)
{
var list = new List<SD>();
var q = (from s in list
where s.ID == ID &&
s.Time >= startDate && s.Time <= endDate
select s).Distinct(new SD.Comparer()).ToList();
return q;
}
EDIT
The regular query uses the default (reference equality) for checking whether two objects are equal or not.
So, in order to make Distinct() work the way you want, you have to implement IEqualityComparer<T>. Otherwise, it will use the default (reference equality) for checking, which means it would only determine the elements were not distinct if they were the same exact instance. See the reference here.
Related
I am attempting to replicate the following SQL query in LINQ using the lambda expression format (to keep it consistent with the code developed so far):
SELECT *
FROM Product p
WHERE p.DateObsolete IS NULL
OR p.DateObsolete > GETDATE()
OR EXISTS (
SELECT NULL
FROM dbo.Product p1
WHERE p1.Ref01 = p.Ref01
AND p1.Ref02 = p.Ref02
AND p1.Ref03 = p.Ref03
AND p1.Version = p.Version + 1
AND p1.DateApproved IS NULL
)
Having looked at other questions (Linq subquery same table using lambda was the closest I could find but but didn't show how to "or" conditions) on SO and elsewhere I thought the following would work but it just causes a stack overflow (seriously) exception and a message about a pdb file not being loaded, which I think is a bit of a red herring.
products = products
.Where(p => !p.DateObsolete.HasValue
|| p.DateObsolete > DateTime.Now
|| products.Any(p1 => p1.Ref01 == p.Ref01
&& p1.Ref02 == p.Ref02
&& p1.Ref03 == p.Ref03
&& p1.Version == p.Version + 1
&& p1.DateApproved == null));
products is an IQueryable variable.
Product is defined in this context as DbSet of:
public class Product
{
public int ProductID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
[MaxLength(3)]
public string Ref01 { get; set; }
[MaxLength(3)]
public string Ref02 { get; set; }
public int Ref03 { get; set; }
public int Version { get; set; }
public DateTime? DateReceivedByGraphics { get; set; }
public DateTime? DateApproved { get; set; }
public DateTime? DateObsolete { get; set; }
public bool Deleted { get; set; }
public bool Discontinued { get; set; }
}
As you may gather I'm new to LINQ (and to posting questions on SO) so any help would be gratefully received.
You might be able to accomplish this with a Join.
DateTime date = DateTime.Today; // or .Now
var products = context.Products.Join(context.Products,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
This joins Product to itself on Ref 1-3, but then selects the "left" side project, along with it's version, the "right" side's version and date approved. The Where condition isolates cases where the "right" version is 1 greater than the left and has no date approved. The result will be the "left" products that have counterparts that match those criteria.
Update:
If you have already filtered the products down to a known set of applicable products, then this will work against Objects. For example:
// given products is an IQueryable representing the filtered products...
DateTime date = DateTime.Today; // or .Now
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
productList = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
If your goal is to try and keep this as an IQueryable scoped to EF then I'd suspect that if it's possible, it might not be worth the complexity/time. Worst-case if you did want to preserve the IQueryable, use the above to select Product IDs into a list, then apply that list as a filter against the IQueryable.
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
// Fetch a list of applicable product IDs.
var productIds = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { ProductId = p1.ProductId, DateObsolete = p1.DateObsolete, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.ProductId)
.ToList();
// Filter the original IQueryable.
products = products.Where(x => productIds.Contains(x.ProductId));
It was as Aleks Andreev and Ivan Stoev suggested that assigning the expression to a new variable sorted out the problem. I'm not sure why this didn't work the first time but my guess is that, after completing the query I tried to re-assign the result back to the original variable - in order not to have to change the variable name in all the code that followed my change.
I am trying to count no of leaves condition year and month here is my LINQ query-
leaves = ( from x in obj.Result
where (Int64.Parse(x.status) == 1 &&
((x.start_date.Month == month &&
x.start_date.Year == selectedyear) &&
(x.end_date.Year == selectedyear || x.end_date.Month == month)))
orderby x.user_id
select x).ToList();
and I am getting a perfect result but now my start_date and end_date becomes
the array of dates, in that array multiple leaves dates are stored
now I want to check my all record within that array condition year and month
here is my model example
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
public List<string> Leaves_Date { get; set; }
and here also leaves_Date would be a string so someone has an idea that how can I set array in my LINQ query
I have the following get method:
public ActionResult InstructorEventsAttended()
{
//Populate the list
var instructors = from s in db.Instructors
orderby db.InstructorEvents.Where(x => x.InstructorId == s.InstructorId && x.AttendanceId == 4).Count() descending
select s;
var viewModel = instructors.Select(t => new StatsInstructorEventsAttendedViewModel
{
Name = t.FirstName + " " + t.LastName,
EventsAttendedF = db.InstructorEvents.Where(x => x.InstructorId == t.InstructorId && x.AttendanceId == 4 && x.Event.EventTypeId == 1).Count(),
EventsAttendedFPer = (db.InstructorEvents.Where(x => x.InstructorId == t.InstructorId && x.AttendanceId == 4 && x.Event.EventTypeId == 1).Count() / db.Events.Where(x => x.EventDate >= t.Joined && x.EventTypeId == 1 && x.EventStatusId == 2).Count()) * 100,
});
return PartialView("_InstructorEventsAttended", viewModel);
}
The view model is:
public class StatsInstructorEventsAttendedViewModel
{
public int InstructorId { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "Fundraising")]
public decimal EventsAttendedF { get; set; }
[Display(Name = "Fundraising")]
public decimal EventsAttendedFPer { get; set; }
}
However the Initialiser for EventsAttendedPer calculates as zero because the first count is smaller than the second. I'm aware that the way to fix this is to convert the numbers to decimal but when I try Convert.ToDecimal I get an error:
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Decimal ToDecimal(Int32)' method, and this method cannot be translated into a store expression.
How can I convert the Counts to Decimal to allow the correct division result?
All of that code boils down to "I want 3 / 2 to return 1.5", right? Cast to a floating point data type, for example double:
(double)myCount1 / (double)myCount2 * 100.0
Entity Framework still does not recognize the Convert.* methods. They are to be avoided anyway. They are a code smell. Usually, a normal cast is the right answer because it is simple.
I have defined a super class "Validity" which defines a time span (ValidFrom / ValidTo) in which an object is "valid". It also defines a function that returns true for a given timestamp, iff (=if and only if) a (derived) object is valid at this time.
public class Validity
{
public int ValidityID { get; set; }
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
bool isValidAt(DateTime time)
{
return (ValidFrom == null || ValidFrom >= time)
&& (ValidTo == null || ValidTo < time);
}
}
Now I would like to write some function that checks isValidAt within a LINQ query. I guess this is possible via IQueriable, but I didn't find out how...
The following code snipped is what I want to have "working" in some way (especially the where n.isValidAt(t)). So, how can this be achieved?
public class Node : Validity {
public int NodeID { get; set; }
public static getFirstNode(DateTime t)
{
MyContext db = new MyContext();
var items = from n in db.Nodes
where n.isValidAt(t)
orderby n.NodeID descending
select n;
return items.FirstOrDefault<Node>();
}
}
--- WORKING SOLUTION ---
I needed to adapt the solution of Zaid Masud a bit, to get it working. Note that I had to remove the this in the parameter list (now the method definition is public static IQueryable<T> isValidAt<T>(IQueryable<T> query, DateTime time) where T : Validity). Here is the source code:
public class Validity
{
public int ValidityID { get; set; }
public DateTime? ValidFrom { get; set; }
public DateTime? ValidTo { get; set; }
public static IQueryable<T> isValidAt<T>(IQueryable<T> query, DateTime time) where T : Validity
{
return query.Where<T>(c => (c.ValidFrom == null || c.ValidFrom >= time)
&& (c.ValidTo == null || c.ValidTo < time));
}
}
You need to declare your bool isValidAt(DateTime time) method as protected so the derived class can access it:
protected bool IsValidAt(DateTime time)
However, after you get this compiling, I doubt that your LINQ to SQL provider will be able to translate the query to SQL. You will probably need to embed the logic inside your LINQ query and write something like:
var items = from n in db.Nodes
where (n.ValidFrom == null || n.ValidFrom >= t) && (n.ValidTo == null || n.ValidTo < t)
orderby n.NodeID descending
select n;
This will work, but a better alternative is to create the following kind of extension method:
public static class ValidityExtensions
{
public static IQueryable<T> Valid<T>(this IQueryable<T> validities, DateTime time) where T : Validity
{
return validities.Where(v => (v.ValidFrom == null || ValidFrom >= time) && (v.ValidTo == null || v.ValidTo < time));
}
}
Now you can use this as follows:
var items = from n in db.Nodes.Valid(time)
orderby n.NodeID descending
select n;
I've seen this topic BETWEEN EQUIVALENT in LINQ
My Original Query in SQL:
SELECT ISNULL(Tcar.name, '') FROM dbo.models model
LEFT JOIN cars Tcar on Tcar.model = model.id AND
Tcar.year between model.Start and model.End
I need to implement between inside a "left join", I tried this:
My Classes:
public class car
{
public string name { get; set; }
public int model { get; set; }
public DateTime year { get; set; }
}
public class model
{
public int id { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
My Implementation:
var theModel = from model in models
join Tcar in cars
on new
{
ID = (int)model.id,
DateStart = (DateTime)model.Start,
DateEnd = (DateTime)model.End
}
equals new
{
ID = (int)Tcar.model,
DateStart = (DateTime)Tcar.year,
DateEnd = (DateTime)Tcar.year
} into tempCar
from finalCar in tempCar
select new
{
CAR = (finalCar == null ? String.Empty : finalCar.name)
};
WorkAround:
var theModel = from model in models
join Tcar in cars
on model.id equals Tcar.model
where model.Start <= Tcar.year && model.End >= Tcar.year
select new
{
CAR = Tcar.name
};
If I use a workaround Linq translate to this query:
SELECT Tcar.name FROM dbo.models model
LEFT JOIN cars Tcar on Tcar.model == model.id
WHERE model.Start <= Tcar.year and model.End >= Tcar.year
I can put a simple where before "select new", but I have to implement by this way, with "between" inside the left join, How can I do this ?
Edit - Added DefaultOrEmpty() in order for it to be a Left Join
Modify your query like so, this will force the where clause into the join on clause. It wont give you the Between clause in the Join, but at least there wont be a where clause
var theModel = from model in models
from Tcar in cars.Where(x => model.id == x.model)
.Where(x => model.Start <= x.year && model.End >= x.year)
.DefaultOrEmpty()
select new
{
CAR = Tcar.name
};
SQL Server should consider your original query and the example produced by LINQ to be identical, because WHERE model.Start <= Tcar.year and model.End >= Tcar.year and ON Tcar.year between model.Start and model.End both specify join conditions.
It's generally preferred to use ON because it keeps your join conditions separate from your other search criteria, but that's for readability rather than performance. Testing similar queries on a couple of tables I had lying around produces identical query plans, and I'd be surprised if you see different plans for your tables.