I am accustomed to using SQL left joins to get a list of all available options and determine what items are currently selected. My table structure would look like this
Table MainRecord -- recordId | mainRecordInfo
Table SelectedOptions -- recordId | optionId
Table AvailableOptions -- optionId | optionValue
and my query would look like this
SELECT optionValue, IIF(optionId IS NULL, 0, 1) AS selected
FROM AvailableOptions AS a
LEFT JOIN SelectedOptions AS s ON s.optionId = a.optionId AND a.recordId = 'TheCurrentRecord'
I am trying to replace this with Entity Framework, so I need help with both a model and a query -- they both need corrected.
public class MainRecord
{
public int recordId { get; set; }
public string mainRecordInfo { get; set; }
[ForeignKey("recordId")]
public List<SelectedOptions> selectedOptions { get; set; }
}
public class SelectedOptions
{
public int recordId { get; set; }
public int optionId { get; set; }
}
public class AvailableOptions
{
public int optionId { get; set; }
public string optionValue { get; set; }
}
Query
IQueryable<AvailableOptions> options = from o in context.AvailableOptions select o;
I can get a list of AvailableOptions, but how can I get a list and know which ones are selected?
If the number of selections and available options is small enough, you can do this in memory:
var selected = options.Join(record.selectedOptions, ao => ao.optionId, so => so.optionId, (a, s) => new { Available = a, Selected = s });
selected will now be a list of objects with Available and Selected as properties and will only contain those that matched in optionId value.
If you only wish to get a pure list of AvailableOptions that match, simply chain a Select to the join:
var selected = options.Join(record.selectedOptions, ao => ao.optionId, so => so.optionId, (a, s) => new { Available = a, Selected = s })
.Select(o => o.Available);
Not a complete answer, but it is really good to understand the navigational properties that you get from the model. Here is a query that most likely isn't exactly what you want but that demonstrate it
from ao in _db.AvailableOptions
where ao.recordId == "TheCurrentRecord" && ao.SelectedOptions.OptionId == 1
select new
MyPoxo {ao.SelectedOptions.Value ?? 0};
so instead of just having o you navigate through the joins that gets specified by the FKs. In this example I would assum AvailableOptions would have a linke to SelectedOptions.
Related
This query gets all the rows from the join table, TagRecipes (many-to-many), where the TagId is found in a list, tagIdList, and lastly just returns the Recipe. How can I make it so it only returns Recipes that have all the tags in the list, tagIdList?
Basically, it's filtering by 'or', but I want it to filter by 'and'. The recipe must contain ALL the tags, not just some.
var allRecipes = await _context.TagRecipes
.Where(tr => tagIdList.Contains(tr.TagId))
.Select(i => i.Recipe).Distinct()
.ToListAsync();
e.g, tagIdList = {17, 20 ,21 }
TagRecipes
So, it should only return Recipe with RecipeId = 2, even though RecipeID 4 contains TagId 17
Classes
public class Recipe
{
public int Id { get; set; }
public string Name { get; set; }
public string Ingredients { get; set; }
public string Instructions { get; set; }
public string ImageURL { get; set; }
public string Description { get; set; }
public ICollection<TagRecipe> TagRecipes { get; set; }
public virtual ICollection<StarRating> StarRatings { get; set; }
public ICollection<Binder> Binders { get; set; }
}
public class TagRecipe
{
public int TagId { get; set; }
public int RecipeId { get; set; }
public Tag Tag { get; set; }
public Recipe Recipe { get; set; }
}
Thank you
group TagRecipes by RecipId, so each RecipId (Key) has its own tagIds.
loop on each group to check if it has all the tags in the provided tagIdList, and if it has them all, store this RecipId (Key), in my case i created list of int RecipIds.
get all the Recipes in the RecipIds list.
I hope this could be helpful
List<int> RecipIds = new List<int>();
int count = 0;
var RecipGroup = _context.TagRecipes.GroupBy(tr => tr.RecipeId);
foreach (var group in RecipGroup)
{
count = 0;
foreach (var tr in group)
{
if (tagIdList.Contains(tr.TagId))
{
count += 1;
}
}
if (tagIdList.Length == count)
{
RecipIds.Add(group.Key);
}
}
var allRecipes = _context.Recipes.Where(r => RecipIds.Contains(r.Id)).ToList();
I believe the following solution using Linqkit will be the simplest way to solve this, and without returning duplicates.
var tagIdList = new List<int> {1, 2};
var predicate = tagIdList.Aggregate(PredicateBuilder.New<Recipe>(), (pred, currentTagId) =>
pred.And(recipe => recipe.TagRecipes.Any(x => x.TagId == currentTagId)));
var result = _context.Recipes.Where(predicate).ToList();
Generates this SQL:
SELECT "r"."Id", "r"."Name"
FROM "Recipes" AS "r"
WHERE EXISTS (
SELECT 1
FROM "TagRecipes" AS "t"
WHERE ("r"."Id" = "t"."RecipeId") AND ("t"."TagId" = #__currentTagId_0)) AND EXISTS (
SELECT 1
FROM "TagRecipes" AS "t0"
WHERE ("r"."Id" = "t0"."RecipeId") AND ("t0"."TagId" = #__currentTagId_1))
Code is tested and verified using an asp.net Core 5 app.
Basically, it's filtering by 'or', but I want it to filter by 'and'.
The recipe must contain ALL the tags, not just some.
So, it should only return Recipe with RecipeId = 2, even though RecipeID 4 contains
TagId 17
Since you want to filter the data by and, after filtering the data based on the tag, if group the result based on the RecipeId property, the item count in the group should be the equally with the tag list count. So, you could use the following query statement:
var tagIdList = new List<int>() { 17, 20, 21 };
var allRecipes = _context.TagRecipes.Include(tr => tr.Recipe)
.Where(tr => tagIdList.Contains(tr.TagId))
.ToList()
.GroupBy(tr=> tr.RecipeId)
.Where(c => c.Count() == tagIdList.Count)
.Select(i => new { RecipeId = i.Key, Count = i.Count(), Recipe = i.FirstOrDefault().Recipe })
.ToList();
The result as below:
How can I get a List<Type1> which includes another List<Type2> from another List<Type3>?
Here is the situation:
I have a List<DbStruncture>. Each entry includes a DatabaseStructure
public partial class DatabaseStructure
{
public string TableSchema { get; set; }
public string TableName { get; set; }
public string ColumnName { get; set; }
public bool? IsPrimaryKey { get; set; }
}
I also have
public class Table
{
public string Name { get; set; }
public string Schema { get; set; }
public List<Column> Columns { get; set; }
}
public class Column
{
public string Name { get; set; }
public bool? IsPrimaryKey { get; set; }
}
Now I want to fill the Data from the List<DatabaseStructure> into a List<Table> which includes a List<Column> with all the Columns of that Table.
I tried it with LINQ and this is how far I got:
var query =
from t in result
group t.TableName by t.TableName
into tn
select new
{
Table = tn.Key,
Schema = from s in result where s.TableName == tn.Key select s.TableSchema.First(),
Columns = from c in result where c.TableName == tn.Key select new Column
{
Name = c.ColumnName,
IsPrimaryKey = c.IsPrimaryKey
}
};
The problem with my solution is, that my query is not a generic List...
Can anybody point me into the right direction? Is LINW the right way here? If yes, how do I get the wanted result?
Thanks in advance
Preface: I prefer (and recommend) using Linq with the Extension Method syntax instead of using the from,group,into keywords because it's more expressive and if you need to do more advanced Linq operations you'll need to use Extension Methods anyway.
To begin with, your input is denormalized (I presume the output of running SELECT ... FROM INFORMATION_SCHEMA.COLUMNS) where each row contains repeated table information, so use GroupBy to group the rows together by their table identifier (don't forget to use both the Table Schema and Table Name to uniquely identify a table!)
Then convert each group (IGrouping<TKey: (TableSchema,TableName), TElement: DatabaseStructure>) into a Table object.
Then populate the Table.Columns list by performing an inner Select from the IGrouping group and then .ToList() to get a concrete List<Column> object.
My expression:
List<DatabaseStructure> input = ...
List<Table> tables = input
.GroupBy( dbs => new { dbs.TableSchema, dbs.TableName } )
.Select( grp => new Table()
{
Name = grp.Key.TableName,
Schema = grp.Key.TableSchema,
Columns = grp
.Select( col => new Column()
{
Name = col.Name,
IsPrimaryKey = col.IsPrimaryKey
} )
.ToList()
} )
.ToList()
OK, just found the answer myself.
Here it is:
var query =
(from t in result
group t.TableName by t.TableName
into tn
select new Table
{
Name = tn.Key,
Schema = (from s in result where s.TableName == tn.Key select s.TableSchema).First(),
Columns = (from c in result
where c.TableName == tn.Key
select new Column
{
Name = c.ColumnName,
IsPrimaryKey = c.IsPrimaryKey
}).ToList()
});
I have a list of objects, TargetList populated from the database which I want to group together based on the AnalyteID, MethodID and InstrumentID fields, but the Unit fields will be stored in a list applicable to each grouped object.
Furthermore, it is only possible for one of the available units to have a target assigned to it. Therefore, during the grouping I need a check to see if a target is available and, if so, skip creation of the unit list.
The TargetList object contains the following attributes:
public int id { get; set; }
public int AnalyteID { get; set; }
public string AnalyteName { get; set; }
public int MethodID { get; set; }
public string MethodName { get; set; }
public int InstrumentID { get; set; }
public string InstrumentName { get; set; }
public int UnitID { get; set; }
public string UnitDescription { get; set; }
public decimal TargetMean { get; set; }
public List<Unit> Units { get; set; }
I have a method for multi-grouping using LINQ:
TargetList.GroupBy(x => new { x.AnalyteID, x.MethodID, x.InstrumentID })...
But unsure as to how to check for a target at a row before extracting all available units at current group if target doesn't exist.
I created a solution which groups all rows returned from the database based on the AnalyteID, MethodID and InstrumentID ('names' of each of these are included in the grouping aswell).
Additionally, all non-unique Unit attributes (UnitID and UnitDescription) are placed into a list only if the TargetMean is 0.
targetViewModel.TargetList
// Group by unique analyte/method/instrument
.GroupBy(x => new { x.AnalyteID, x.AnalyteName, x.MethodID, x.MethodName, x.InstrumentID, x.InstrumentName })
// Select all attributes and collect units together in a list
.Select(g => new TargetView
{
id = g.Max(i => i.id),
AnalyteID = g.Key.AnalyteID,
AnalyteName = g.Key.AnalyteName,
MethodID = g.Key.MethodID,
MethodName = g.Key.MethodName,
InstrumentID = g.Key.InstrumentID,
InstrumentName = g.Key.InstrumentName,
TargetMean = g.Max(i => i.TargetMean),
UnitID = g.Max(i => i.UnitID),
UnitDescription = g.Max(i => i.UnitDescription),
// only extract units when target mean is 0
Units = g.Where(y => y.TargetMean == 0)
.Select(c => new Unit { ID = c.UnitID, Description = c.UnitDescription }).ToList()
}).ToList();
Note: The Max method is used to extract any required non-key attributes, such as the TargetMean/id. This works fine because only one row will ever be returned if a TargetMean exists.
It does feel 'dirty' to use the Max method in order to obtain all other non-key attributes though so if anyone has any other suggestions, please feel free to drop an answer/comment as I am interested to see if there are any cleaner ways of achieving the same result.
Lets say I have two lists objects with this classes
class Sku
{
public string skuId { get; set; }
public int qty { get; set; }
public string city { get; set; }
}
class SkuWithCity
{
public string skuId { get; set; }
public string city { get; set; }
}
And I have two lists with objects:
List<Sku> skuList = new List<Sku>();
List<SkuWithCity> skuListWithCity = List<SkuWithCity>();
Imagine that in the first list(skuList), the property "City" of each object is null.
What I want to do is, using linq, select the sku objects that have the same skuId and add the city. Somethis like:
var result = from skuElements in skuList
from skuWCity in skuListWithCity
where skuElements.sku == skuWCity.sku
select skuElements
{
skuElements.city = skuWCity.city,
};
And get the whole object, not just the city
int order to get:
|Object | Qty | City
|---- |----|
|AAA | 2 | Panama|
|BBB | 5 | Rio De Janeiro|
is this even possible, get the whole object and modify one or many properties?
UPDATE: In real life the object that I'm trying to copy has a lot of members, that is why I'm trying to "copy" de object of list A and just modify some attributes using the match object of list B.
If you just want to update the existing objects instead of projecting to a new set of objects then first use a join to get the matching items.
var result = from skuElement in skuList
join skuWCity in skuListWithCity
on skuElements.skuId == skuWCity.skuId
select skuElements
{
skuElement,
skuWCity
};
Then iterate them and do the update.
foreach(var x in result)
{
x.skuElement.City = x.skuWCity.City;
}
Note this does not handle the case where more than one item in either list has more than one match in the other. I'm assuming that the lists have a one-to-one match already.
Alternatively you could just use a dictionary.
var cities = skuListWithCity.ToDictionary(s => s.skuId, s => s.City);
foreach(var s in skuList)
{
s.City = cities[s.skuId];
}
Note that this fails if there are duplicate skuId in skuListWithCity or if a skuId in skuList is not in skuListWithCity
You could use a join and then make a projection of the result set as below:
var result = from skuElements in skuList
join skuWCity in skuListWithCity
on skuElements.skuId equals skuWCity.skuId
select new Sku
{
skuId = skuElements.skuId,
qty = skuElements.qty,
city = skuWCity.city,
};
I have two objects: PhraseCategory and Phrase. Here's the classes:
public class PhraseCategory
{
public System.Guid PhraseCategoryId { get; set; } // PhraseCategoryId
public int PhraseCategoryShortId { get; set; } // PhraseCategoryShortId (Primary key)
public int PhraseCategoryGroupId { get; set; } // PhraseCategoryGroupId
public string Name { get; set; } // Name (length: 20)
// Reverse navigation
public virtual System.Collections.Generic.ICollection<Phrase> Phrases { get; set; } // Phrase.FK_PhrasePhraseCategory
}
public class Phrase : AuditableTable
{
public System.Guid PhraseId { get; set; } // PhraseId (Primary key)
public string English { get; set; } // English
public int? CategoryId { get; set; } // CategoryId
// Foreign keys
public virtual PhraseCategory PhraseCategory { get; set; } // FK_PhrasePhraseCategory
}
Can someone tell me how I could join these so that I am able to select all the phrases with for example a PhraseCategoryGroupId of 25.
Here's what I have right now but it does not take into account my need to also be able to select the Phrases with a PhraseCategory that has a PhraseCategoryGroupId:
List<Phrase> phrases;
var query = db.Phrases.AsQueryable();
if (options.CreatedBy != 0) query = query
.Where(w => w.CreatedBy == options.CreatedBy);
phrases = await query
.AsNoTracking()
.ToListAsync();
return Ok(phrases);
Note that I would like to get just a flat output (hope that makes sense). What I mean is a list that contains just:
PhraseId, English and CategoryId
This should get you what you need:
phrases = phrases.Where( x => x.PhraseCategory.PhraseCategoryGroupId == 25 )
.Select( x => new
{
PhraseId = x.PhraseId,
English = x.English,
CategoryId = x.CategoryId
});
Please note that you can also create instances of another type instead of the anonymous type which I am creating in the above query.
Also, the PhraseCategory will be lazy loaded in the above query since you have lazy loading enabled on the property: it is virtual. If you have lazy loading disabled globally, then you will need to use the Include method in your query. Then your query will become:
phrases = phrases.Include(x => x.PhraseCategory)
.Where( x => x.PhraseCategory.PhraseCategoryGroupId == 25 )
.Select( x => new
{
PhraseId = x.PhraseId,
English = x.English,
CategoryId = x.CategoryId
});