LINQ to SQL selective choosing of fields from a group - c#

I have a LINQ to SQL query which at one point, returns two rows which I'd like to group by ID and select specific fields, lets say something like this:
ID | Field1 | Field2 | Field3
-----------------------------
1 | 2 | null | 4
1 | 3 | 5 | null
What's unusual about this query is that from the group, I want to select something like this:
ID | Field1 | Field2 | Field3
-----------------------------
1 | 2 | 5 | 4
Note that the group selects fields from both records. Also note that it's not a simple null coalesce type operation as I need to be able to select a Field1 of 2 or 3 based on some other factors (another field).
Is there a clever way I can project into a new type whilst selecting specific fields as I want them?
I'm working on an IQueryable at this point, so I need a solution which translates into SQL, previously the query did something like this:
.Select(x => new MyObject {
Field1 = myGroup.First(x => x.Field4 == 1).Field1,
Field2 = myGroup.FirstOrDefault(x => x.Field4 == 1) == null ? myGroup.FirstOrDefault(x => x.Field4 == 2).Field2 : myGroup.FirstOrDefault(x => x.Field4 == 1).Field2
});
Which doesn't work as it can't work out what to with the operations.
I think we might be resigned to selecting some combination object with all the fields denormalised and then selecting from LINQ to objects in memory, but I'd rather have the DB do the work if possible.

What you need to do is write the C#/LINQ expression to generate a SQL CASE statement.
Generally speaking a case statement is written:
M = CASE
WHEN A == P THEN V
WHEN B == Q THEN W
WHEN C == R THEN X
WHEN D == S THEN Y
ELSE Z
END
In these examples all the letters A = P to D = S, and V to Z can be any expression of any complexity. So for example you can say WHEN a.cost > 1.5 * b.estimate THEN c.bestoffer.
To generate that exact case statement you need the following LINQ/C#:
M =
(
A == P? V :
B == Q? W :
C == R? X :
D == S? Y :
Z
)
So to attack your (unstated) problem directly, you can write arbitrary conditions for the values of each field, using ternary operators and these will be converted to LINQ CASE statements.
See also (and credit to) to the following:
http://www.codeproject.com/Articles/38264/How-to-Create-T-SQL-CASE-Statements-With-LINQ-To-S
linq case statement

If I understand what you're asking for, should be something like this:
from g in (from entity in db.Entities
group entity by entity.Id)
select new
{
Id = g.Key,
Field1 = g.Select(e => e.Field1)
.FirstOrDefault(f => null != f),
Field2 = g.Select(e => e.Field2)
.FirstOrDefault(f => null != f),
Field3 = g.Select(e => e.Field3)
.FirstOrDefault(f => null != f),
}
Note that if – for any given Fieldx – there is no non-null value of that field within that group, the resultant Fieldx value will be null.

Related

Find the first free id

One of my small database management projects (written in delphi) used sql queries to find the first free id of mysql table.
Example: I have to find the first free id (hole) in a table like this:
| id | Col1 |
|------|------|
| 5101 | ABC |
| 5102 | BCD |
| 5103 | CDE |
| 5105 | EFG | 🡔 first missing id
| 5106 | GHI |
| 5108 | ILM |
The code should find the first free id 5104
Here's how I'd do it in SQL (in old project):
SELECT
MIN((doc.id + 1)) AS nextID
FROM (doc
LEFT JOIN doc doc1
ON (((doc.id + 1) = doc1.id)))
WHERE (ISNULL(doc1.id) AND (doc.id > 5000))
Now, which I am rewriting in c # language, I need to convert sql statements into a LINQ query (which uses Devart dotConnect for mysql Entity Framework).
Starting from here:
DC db = new DC();
var nums = db.Documentos.OrderBy(x => x.Id);
From Can LINQ be used to find gaps in a sorted list?:
var strings = new string[] { "7", "13", "8", "12", "10", "11", "14" };
var list = strings.OrderBy(s => int.Parse(s));
var result = Enumerable.Range(list.Min(), list.Count).Except(list).First(); // 9
Basically, order the list. Then create an array of sequential numbers (1,2,3...) from the minimum all the way to the max. Check for missing values in the list, and grab the first one. That's the first missing number.
This can give you all gaps within your table
var nums= (new List<int> (){1,2,3,25,4,5,6,7,8, 12, 15,21,22,23}).AsQueryable();
nums
.OrderBy(x => x)
.GroupJoin(nums, n=> n + 1, ni => ni, (o,i)=> new {o, i})
.Where(t=> !(t.i is IGrouping<int, int>))
.Dump();
.Net Fiddle
Another method (similar to what you're using now).
Assume you have an array of integers (or another type of collection) like this:
var myIDs = new int[] { 5101, 5113, 5102, 5103, 5110, 5104, 5105, 5116, 5106, 5107, 5108, 5112, 5114, 5115 };
If it's not already ordered, the OrderBy() it:
myIDs = myIDs.OrderBy(n => n).ToArray();
Extract the first number that is less than (next number) + 1:
int result = myIDs.Where((n, i) => (i < myIDs.Length - 1) && (n + 1 < myIDs[i + 1])).FirstOrDefault();
If none of the members of this collection satisfy the condition, take the last one and add 1:
result = result == default ? myIDs.Last() + 1 : result;

How can I return a single value integer from Linq from an array of objects that have other nested values

I am trying to do a Linq query on a collection of dynamic objects based on the value of a sub-sub field, and then return the single value of a different field as an integer.
What I have so far is :
int itemId = (
from x in ((IEnumerable<dynamic>)allpets.pets.collected)
where x.stats.speciesId == 294
select x.itemId
).SingleOrDefault()
The problem is, sometimes the result found has no field x.itemId which ends up causing an exception.
'System.Dynamic.DynamicObject' does not contain a definition for 'itemId'
I have tried x?.itemId , x.?itemId , x.itemId? , and ?x.itemId which seem to be the only places I can capture for nulls.
The other part, is this is part of a more complex nested linq select, and where the value 294 is located, is actually p.stats.speciesId (snippet below so you can see why this needs to be inline)
List<MasheryTypes.pet> pets = ((IEnumerable<dynamic>)json.pets).Select(
p => new MasheryTypes.pet(
Convert.ToBoolean(p.canBattle),
p.creatureId,
p.name,
p.family,
p.icon,
p.qualityId,
new MasheryTypes.petstats(
p.stats.speciesId,
p.stats.breedId,
p.stats.petQualityId,
p.stats.level,
p.stats.health,
p.stats.power,
p.stats.speed
),
p.strongAgainst?[0],
p.typeId,
p.weakAgainst?[0],
cageable.Any(
c => c == p.creatureId
),
p.itemId = (from x in ((IEnumerable<dynamic>)allpets.pets.collected)
where x.stats.speciesId == p.stats.speciesId
select x.itemId).SingleOrDefault()
)
).ToList();
Try this.
int itemId = (
from x in ((IEnumerable<dynamic>)allpets.pets.collected)
where x.GetType().GetProperty("itemId") != null && x.stats.speciesId == 294
select x.itemId
).SingleOrDefault()

Entity Framework IQueryable, with 2 queries

I wanted to ask if there is a better way to achieve what I'm trying to do.
I need to get all the records of a child graph that have the date greter or equal to the given date, and the first record with date minor to the given date.
I found this solution that works but I'm not sure that this will be the best way.
var q = context.Istc0.Include("Interests").Where(a => a.IIsin == listKey).Select(a => new
{
Istc0 = a,
Interests = a.Interests.Where(d => d.InDat >= date)
});
var qq = context.Istc0.Include("Interests").Where(a => a.IIsin == listKey).Select(a => new
{
Istc0 = a,
Interests = a.Interests.Where(d => d.InDat < date).OrderByDescending(d => d.InDat).Take(1)
});
var xxx = q.ToList()[0].Istc0;
xxx = qq.ToList()[0].Istc0;
return xxx;
I do not know which one you need to return. Probably yyy.
var q = context.Istc0.Include("Interests").Where(a => a.IIsin == listKey).Select(a => new
{
Istc0 = a,
Interests = a.Interests.Where(d => d.InDat >= date)
}).ToList();
var xxx = q[0].Istc0;
var yyy = q.OrderByDescending(d => d.InDat).Take(1).SingleOrDefault().Istc0;
Dictionary<string,decimal> result = new Dictionary<string,decimal>();
result.Add("all",xxx);
result.Add("previous",yyy);
return result;
In this example i create a Dictionary with a string key (could be an integer or whatever you like) and with a decimal(i am guessing the returned value is of decimal type) value to store the results of the queries. Then i return this dictionary.
Another way would be to have a strongly typed object and return a list of that object.
Lastly, you could define two output parameters (read this). For instance:
public void GetInterestRates(string listKey, out decimal currentRate, out decimal previousRate)
{
var q = context.Istc0.Include("Interests").Where(a => a.IIsin == listKey).Select(a => new
{
Istc0 = a,
Interests = a.Interests.Where(d => d.InDat >= date)
}).ToList();
var currentRate = q[0].Istc0;
var previousRate = q.OrderByDescending(d => d.InDat).Take(1).SingleOrDefault().Istc0;
}
And when you want to use this:
decimal currentRate , previousRate;
GetInterestRates(listKey, currentRate , previousRate);
Hi thanks for the answer.
With the query you posted I don't get all the data I need. I don't think that I can get what I need with one query.
Interest table
-----------------
2013-5-16 | 1%
2013-6-21 | 0.8%
2013-7-12 | 0.5%
2013-8-06 | 0.6%
The istc0 table contains all my isin number, and as a relationship with the interest table One to Many.
On of the parameter of the function is the date. so if I pass for example
date = 2013-7-01
the result I need is the following:
Interest table
-----------------
2013-6-21 | 0.8%
2013-7-12 | 0.5%
2013-8-06 | 0.6%
So in my forst post the first query assigned to q retrieves all the interests after july the first, the second query assigned to qq retrieves the first interest rate before july the first.
My function does the job, and I get the set of data that I need, but it doesn't seem to be the cleanest way to do it.

Can you use a CASE statement with OrderBy in an LINQ to Entities query?

I'm wonder if someone can transform the SQL below to a LINQ to Entities query
SELECT Name, IsEmployee, IsQualityNetwork
FROM Person
ORDER BY CASE WHEN IsQualityNetwork = 1 or IsEmployee = 1 THEN 0 ELSE 1 END, Name
I tried using Linq Dynamic but when this code is executed:
var p = ctx.People
.OrderBy("CASE WHEN IsQualityNetwork = 1 or IsEmployee = 1 THEN 0 ELSE 1 END")
.OrderBy(e => e.Name);
I get the exception:
{"No property or field 'CASE' exists in type 'Person'"}
var p = ctx.People.OrderBy(p => (p.IsQualityNetwork == 1 || p.IsEmployee == 1) ? 0 : 1)
.ThenBy(p => p.Name);
Here's a translation of your SQL to LINQ.
var query = from p in ctx.People
let order = p.IsQualityNetwork || p.IsEmployee ? 0 : 1
orderby order, p.Name
select new
{
p.Name,
p.IsEmployee,
p.IsQualityNetwork,
}
I've used the fluent query syntax so I could show you the let keyword. let allows you to declare a range variable that can then be reused in your query, this can be very useful if you've got a conditional that gets used in a lot of places, or if you need to chain multiple conditionals.

Search on all fields of an entity

I'm trying to implement an "omnibox"-type search over a customer database where a single query should attempt to match any properties of a customer.
Here's some sample data to illustrate what I'm trying to achieve:
FirstName | LastName | PhoneNumber | ZipCode | ...
--------------------------------------------------
Mary | Jane | 12345 | 98765 | ...
Jane | Fonda | 54321 | 66666 | ...
Billy | Kid | 23455 | 12345 | ...
If the query was "Jane", I'd expect row #1 to be returned as well as row #2.
A query for 12345 would yield rows #1 and #3.
Right now, my code looks pretty much like this:
IEnumerable<Customer> searchResult = context.Customer.Where(
c => c.FirstName == query ||
c.LastName == query ||
c.PhoneNumber == query ||
c.ZipCode == query
// and so forth. Fugly, huh?
);
This obviously works. It smells like really bad practice to me, though, since any change in the Entity (removal of properties, introduction of new properties) would break stuff.
So: is there some LINQ-foo that will search across all properties of whatever Entity I throw at it?
first find all properties within Customer class with same type as query:
var stringProperties = typeof(Customer).GetProperties().Where(prop =>
prop.PropertyType == query.GetType());
then find all customers from context that has at least one property with value equal to query:
context.Customer.Where(customer =>
stringProperties.Any(prop =>
prop.GetValue(customer, null) == query));

Categories