LINQ create generic List of nestet objects - c#

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

Related

Entity Left Join to Replace SQL

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.

Select categories with subcategories [duplicate]

I have the following entity:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public Item Parent { get; set; }
public List<Item> Children { get; set; }
public double PropertyA { get; set; }
public double PropertyB { get; set; }
...
}
Now I want to query the database and retrieve data of all the nested children.
I could achieve this by using Eager Loading with Include():
var allItems = dbContext.Items
.Include(x => Children)
.ToList();
But instead of Eager Loading, I want to do the following projection:
public class Projection
{
public int Id { get; set; }
public List<Projection> Children { get; set; }
public double PropertyA { get; set; }
}
Is it possible to retrieve only the desired data with a single select?
We are using Entity Framework 6.1.3.
Edit:
This is what I have tried so far.
I really don't know how to tell EF to map all child Projection the same way than their parents.
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
var allItems = dbContext.Items
.Select(x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection()
{
Id = c.Id,
PropertyA = c.PropertyA,
Children = ???
})
})
.ToList();
Generally speaking, you can't load a recursive structure of unknown unlimited depth in a single SQL query, unless you bulk-load all potentially relevant data irregardless whether they belong to the requested structure.
So if you just want to limit the loaded columns (exclude PropertyB) but its ok to load all rows, the result could look something like the following:
var parentGroups = dbContext.Items.ToLookup(x => x.ParentId, x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA
});
// fix up children
foreach (var item in parentGroups.SelectMany(x => x))
{
item.Children = parentGroups[item.Id].ToList();
}
If you want to limit the number of loaded rows, you have to accept multiple db queries in order to load child entries. Loading a single child collection could look like this for example
entry.Children = dbContext.Items
.Where(x => x.ParentId == entry.Id)
.Select(... /* projection*/)
.ToList()
I see only a way with first mapping to anonymous type, like this:
var allItems = dbContext.Items
.Select(x => new {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new {
Id = c.Id,
PropertyA = c.PropertyA,
})
})
.AsEnumerable()
.Select(x => new Projection() {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection {
Id = c.Id,
PropertyA = c.PropertyA
}).ToList()
}).ToList();
A bit more code but will get the desired result (in one database query).
Let's say we have the following self-referencing table:
public class Person
{
public Person()
{
Childern= new HashSet<Person>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(50)]
public string Name{ get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
And for some point of time you need to get all grandsons for specific persons.
So, first of all I will create stored procedure(using code-first migration) to get all persons in the hierarchy for those specific persons:
public override void Up()
{
Sql(#"CREATE TYPE IdsList AS TABLE
(
Id Int
)
GO
Create Procedure getChildIds(
#IdsList dbo.IdsList ReadOnly
)
As
Begin
WITH RecursiveCTE AS
(
SELECT Id
FROM dbo.Persons
WHERE ParentId in (Select * from #IdsList)
UNION ALL
SELECT t.Id
FROM dbo.Persons t
INNER JOIN RecursiveCTE cte ON t.ParentId = cte.Id
)
SELECT Id From RecursiveCTE
End");
}
public override void Down()
{
Sql(#" Drop Procedure getChildIds
Go
Drop Type IdsList
");
}
After that you can use Entity Framework to load the ids(you could modify stored procedure to return persons instead of only returning ids) of persons under the passed persons(ex grandfather) :
var dataTable = new DataTable();
dataTable.TableName = "idsList";
dataTable.Columns.Add("Id", typeof(int));
//here you add the ids of root persons you would like to get all persons under them
dataTable.Rows.Add(1);
dataTable.Rows.Add(2);
//here we are creating the input parameter(which is array of ids)
SqlParameter idsList = new SqlParameter("idsList", SqlDbType.Structured);
idsList.TypeName = dataTable.TableName;
idsList.Value = dataTable;
//executing stored procedure
var ids= dbContext.Database.SqlQuery<int>("exec getChildIds #idsList", idsList).ToList();
I hope my answer will help others to load hierarchical data for specific entities using entity framework.

Can you add a Where() to an IQueryable when the field is not in the selected output

Suppose I have a 2 table join in a function that returns an IQueryable, but the output is a named type that is neither of the two tables:
var qry = from p in Persons
join h in Hobbies on p.PersonId equals h.PersonId
select new OutputType
{
Name = p.FirstName,
Hobby = h.HobbyName
}
return qry
Let's say now I wanted to take this returned query and do something like:
var newQuery = qry.Where( p=>p.Age > 18 )
As you can see this is a problem because the IQueryable is of type OutputType, so I can't add a where to a person's age unless I were to add the Age to OutputType.
Is there anyway of 'breaking into' the IQueryable expression tree and adding a lambda somehow that will query on the source collection specified in it and add a Where clause to it? Or do I have do I have to add a Where field to the OutputType even though I'm uninterested in ultimately projecting it?
It is easier to narrow your view later than to try to backtrack. Here is a stripped down example of how I like to layer methods for reuse so that they spit out nice sql.
private IQueryable<Part> GetParts_Base()
{
//Proprietary. Replace with your own.
var context = ContextManager.GetDbContext();
var query = from c in context.Component
where c.Active
//kind of pointless to select into a new object without a join, but w/e
select new Part()
{
PartNumber = c.ComponentNumber,
Description = c.ComponentDescription,
Cost = c.ComponentCost,
Price = c.ComponentPrice
};
return query;
}
//Exclude cost from this view
public IEnumerable<Part_PublicView> GetParts_PublicView(decimal maxPrice)
{
var query = GetParts_Base();
var results = from p in query
where p.Cost < maxPrice
select new Part_PublicView()
{
PartNumber = p.PartNumber,
Description = p.Description,
Price = p.Price
};
return results;
}
public class Part_PublicView
{
public string PartNumber { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
private class Part : Part_PublicView
{
public decimal Cost { get; set; }
}
Linq-to-entity does not penalize you for selecting the extra column early on. As you can see, the sql includes the Cost column in the constraint but not in the select.
SELECT
1 AS [C1],
[Extent1].[ComponentNumber] AS [ComponentNumber],
[Extent1].[ComponentDescription] AS [ComponentDescription],
[Extent1].[ComponentPrice] AS [ComponentPrice]
FROM [dbo].[Component] AS [Extent1]
WHERE [Extent1].[ComponentCost] < #p__linq__0

Linq to Entities subquery to fill array?

New at linq to entities trying to figure this out. I have the following tables:
Customer: Cust_Id, Name
Orders: Order_Id
CustomerOrders: Cust_Id, Order_Id
I have a class like so:
public class Customers
{
public List<Row> Rows { get; set; }
public Customers()
{
Rows = new List<Row>();
}
public class Row
{
public int Key { get; set; }
public string Name { get; set; }
public List<string> Order_Ids { get; set; }
}
}
Linq query is like this:
var query = from c in context.Customer
select new Customers.Row
{
Key = c.Cust_Id,
Name = c.Name,
Order_IDs = List<string>( ?? )
};
foreach (var row in query)
{
Customers.Rows.Add(row);
}
var serializer = new JavaScriptSerializer();
return serializer.Serialize(Customers);
Where I have '??', can I use a subquery or something to get a list of Order_Id's from the CustomerOrders table?
Right Now, I can only think to loop through the Customers table once it is filled and then query the DB again to get each array of Order Id's for each Customer.
If it's not a requirement, drop the "Row" collection from the "Customer" object. This should suffice:
public class Customer
{
public int Key { get; set; }
public string Name { get; set; }
public List<string> Order_Ids { get; set; }
}
Then you can query like this:
var customers = from c in context.Customers
select new Customer
{
Key = c.Cust_Id,
Name = c.Name,
Order_IDs = c.Orders.Select(o => o.Order_Id).ToList()
};
It's better to deal in objects when writing C# and using EF, than to deal in terms of tables and rows -less confusing.
Try something like this:
var query = from c in context.Customer
select new Customers.Row
{
Key = c.Cust_Id,
Name = c.Name,
Order_Ids = c.Rows.Select(row => row.Key.ToString()).ToList()
};
Where you have .Select(row => row.Key.ToString()) you can set the property you need (Key, Name, etc...). Select method is an extension method to IEnumerable and it return a collection of type of property you have seted, in this case, a collection of strings because I converted it with ToString() method.

How to get a set of values as a list when grouped by a pair of columns

I have a datatable with below values
id Name date
1 a 5/3/2011
1 a 6/4/2011
I want to retrieve the values with a list of associated dates for each id/name pair.
I would suggest you create a class which encapsulates all of that data, and then you can create a List<T> of the appropriate type. You'd create an instance of your new class per entry in the DataTable.
If you use a strongly-typed dataset you could use the generated DataRow type instead, if you wanted.
(It's not clear what you mean by "store in a list as single entry" - the whole table, or one entry per row?)
It's difficult to answer with out the context of the usage. Is this going to be used right a way, communicated to other parts of the system. The below assumes that it's not coomunicated to other parts of the system
var list = (from e in DataTable.Rows.AsEnumerable()
select new {
id = e["id"],
Name = e["Name"],
data = e["data"]
}).ToList()
Create a class that maps onto your table. You can use EntityFramework, LINQ to SQL, nHibernate or a custom ORM to retrieve data from the table as these objects. Select the objects and use the LINQ grouping operator to either create anonymous objects (or another class with the list of dates).
public class Foo
{
public int ID { get; set; }
public sring Name { get; set; }
public DateTime Date { get; set;
}
public class Bar
{
public int ID { get; set; }
public string Name { get; set; }
public List<DateTime> Dates { get; set; }
}
public class FooDataContext : DbContext
{
IDbSet<Foo> Foos { get; set; }
}
using (var context = new FooDataContext())
{
List<Bar> bars = context.Foos
.GroupBy( f => new { f.ID, f.Name } )
.Select( g => new Bar
{
ID = g.Key.ID,
Name = g.Key.Name,
Dates = g.Select( f => f.Date )
});
}

Categories

Resources