LINQ to SQL separation and reusability of data structures - c#

Here is simplified SQL of my tables, which is converted to LINQ to SQL model.
CREATE TABLE Campaign (
Id int PRIMARY KEY,
Name varchar NOT NULL
);
CREATE TABLE Contract (
Id int PRIMARY KEY,
CampaignId int NULL REFERENCES Campaign(Id)
);
Now i have classes like this (these are in different namespace, not entity classes from datamodel).
public class CampaignInfo {
public static CampaignModel Get(DataModel.CampaignInfo campaign) {
return new CampaignInfo {
Id = campaign.Id,
Name = campaign.Name,
Status = CampaignStatus.Get( c )
};
}
public int Id {get; set;}
public int Name {get; set;}
public CampaignStatus { get; set;}
}
public class CampaignStatus {
public static CampaignStatus Get(DataModel.Campaign campaign) {
return new CampaignStatus {
Campaign = campaign.Id, // this is just for lookup on client side
ContractCount = campaign.Contracts.Count()
// There is much more fields concerning status of campaign
};
}
public int Campaign { get; set; }
public int ContractCount {get; set;}
}
And than i am running query:
dataContext.Campaigns.Select( c => CampaignInfo.Get( c ) );
Other piece of code can do something like this:
dataContext.Campaigns.Where( c => c.Name == "DoIt" ).Select( c => CampaignInfo.Get( c ) );
Or i want to get list of statuses for campaigns:
dataContext.Campaigns.Select( c => CampaignStatus.Get( c ) );
Note: Results from those calls are converted to JSON, so there is no need to keep track on original db entities.
As you can see, there are two goals. To have control over what data are taken from database and reuse those structures in other places. However this approach is a huge mistake, because of that classes returning object and using it in expression tree.
Now i understand, it cannot magically create expression to make whole thing with one query. Instead it's getting count for every campaign separately. In rather complex scenarios it's ugly slowdown.
Is there some simple way how to achieve this ? I guess, those classes should return some expressions, but i am totally new to this field and i am not sure what to do.

The general problem, if I understand correctly, is that you have some business logic that you don't want to have repeated throughout your code (DRY). You want to be able to use this logic inside your LINQ methods.
The general solution with LINQ (over Expression trees) is to create a filter or transformation function that returns an IQueryable so you can do further processing on that query, before it is sent to the database.
Here is a way to do this:
// Reusable method that returns a query of CampaignStatus objects
public static IQueryable<CampaignStatus>
GetCampaignStatusses(this IQueryable<Compaign> campaigns)
{
return
from campaign in campaigns
new CampaignStatus
{
Campaign = campaign.Id,
ContractCount = compaign.Contracts.Count()
};
}
With this in place, you can write the following code:
using (var db = new DataModel.ModelDataContext() )
{
return
from campaign in db.Campaigns.WithContractCount()
from status in db.Campaigns.GetCampaignStatusses()
where campaign.Id == status.Campaign
select new
{
Id = campaign.Id,
Name = campaign.Name,
Status = status
};
}
Having an method that returns an IQueryable allows you to do all sorts of extra operations, without that method knowing about it. For instance, you can add filtering:
from campaign in db.Campaigns.WithContractCount()
from status in db.Campaigns.GetCampaignStatusses()
where campaign.Id == status.Campaign
where campaign .Name == "DoIt"
where status .ContractsCount < 10
select new
{
Id = campaign.Id,
Name = campaign.Name,
Status = status
};
or add extra properties to the output:
from campaign in db.Campaigns.WithContractCount()
from status in db.Campaigns.GetCampaignStatusses()
where campaign.Id == status.Campaign
select new
{
OtherProp = campaign.OtherProp,
Id = status.Campaign,
Name = campaign.Name,
Status = status
};
This will be translated to an efficient SQL query. The query will not get more records or columns from the database than strictly needed.

You could use something like:
using( var dataContext = new DataModel.ModelDataContext() )
{
dataContext.Campaigns.Select( c => new {
Id = c.Id,
Name = c.Name,
Status = new CampaignStatus()
{
ContractCount = c.Contracts.Count()
}
})
}
in your query.

Related

How to filter child table by dictionary

I have a question for you all!!!,
Assume that I have 2 tables, one of them is a parent, and the other is a child. I want to filter the parent table by sending data from the child object. The child table contains 4 columns like
{Id, RuleId, RuleKey, RuleValue}
I send a dictionary as a request that includes value for 2 columns ( RuleKey, RuleValue) of the child object. I want to return the parent object that includes RuleKey and RuleValue I sent.
// Key is the column name of the child table and value is the corresponding value
var dictionary = new Dictionary<string,string>()
{
{"RuleKey", "RuleValue"},
{"RuleKey1", "RuleValue1"}
}
This is what I tried but it obviously won't work and still give the syntax error.
These 2 questions about this issue;
1-) How should I do this (Important one:)))))
2-) Will be there any error If I do this In-Memory, If I remember that correctly, EFCore won't translate the LINQ to SQL script because of the memory issues.
var rules = _ruleContext.Rules.AsQueryable();
rules = rules.Where(u => u.Criteria.Where(k => dictionary.Keys.Contains(k.Key) && dictionary.Values.Contains(k.Value)));
public class Rule : Entity<int>
{
public string RuleName { get; set; }
public string Expression { get; set; }
public ICollection<Criteria> Criteria { get; set; }
}
public class Criteria : Entity<int>
{
public int RuleId { get; set; }
public string Name { get; set; }
public string Key { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
public Rule Rule { get; set; }
}
I'm assuming that you want to only match criteria where both Key and Value match a key/value pair in the dictionary. (Your pseudocode would include any rules where either the key or value matched)
What you're trying to do is join between the Criteria table and the key/values in the dictionary. One way to accomplish this would be to create a temporary table containing those pairs, and then use a raw SQL statement to join from Criteria onto that.
Something like this might work:
// To use the temporary table you need to make sure that the connection stays
// open beyond the table creation statement
context.Database.OpenConnection();
try
{
context.Database.ExecuteSqlRaw("CREATE TABLE #RequiredCriteria ([Value] nvarchar(100), [Key] nvarchar(100))");
// If you're expecting LOTs of criteria this could become a bottleneck...
foreach (var criteria in dictionary)
{
context.Database.ExecuteSqlInterpolated($"INSERT INTO #RequiredCriteria ([Value], [Key]) VALUES ({criteria.Key}, {criteria.Value})");
}
// This creates Rule objects from a raw SQL query that does
// the required joining of parameters
var matched = context.Rules.FromSqlRaw(
#"SELECT * FROM Rules
WHERE Id IN (
SELECT DISTINCT RuleId
FROM Criteria C
INNER JOIN #RequiredCriteria RC
ON C.[Key] = RC.[Key] AND C.[Value] = RC.[Value])")
.ToList();
context.Database.ExecuteSqlRaw("DROP TABLE #RequiredCriteria");
}
finally
{
context.Database.CloseConnection();
}
Edit (now without distinct):
I noticed in your comment that a rule must meet all the given criteria - this version of the query will make sure that any matching rule will have the same number of distinct criteria that were requested. That way if the same criteria key and value is duplicated in the request it won't matter:
// To use the temporary table you need to make sure that the connection stays
// open beyond the table creation statement
context.Database.OpenConnection();
try
{
context.Database.ExecuteSqlRaw("CREATE TABLE #RequiredCriteria ([Value] nvarchar(100), [Key] nvarchar(100))");
// If you're expecting LOTs of criteria this could become a bottleneck...
foreach (var item in dictionary)
{
context.Database.ExecuteSqlInterpolated($"INSERT INTO #RequiredCriteria ([Value], [Key]) VALUES ({item.Key}, {item.Value})");
}
var matched = context.Rules.FromSqlInterpolated(
#$"SELECT * FROM Rules
WHERE Id IN (
SELECT RuleId
FROM Criteria C
INNER JOIN #RequiredCriteria RC
ON C.[Key] = RC.[Key] AND C.[Value] = RC.[Value]
GROUP BY RuleId
HAVING COUNT(RuleId) = {dictionary.Count}
)")
.ToList();
context.Database.ExecuteSqlRaw("DROP TABLE #RequiredCriteria");
}
finally
{
context.Database.CloseConnection();
}
Edit 2:
If the Criteria table is going to be quite large, I'd definitely recommend you have an index on the Key and Value columns, otherwise you'll be doing a clustered index scan across the entire table for every query.
You can use function FilterByItems (don't want to repeat myself). Then query can be written in the following way:
var dictionary = new Dictionary<string,string>()
{
{"RuleKey", "RuleValue"},
{"RuleKey1", "RuleValue1"}
}
var matched = _ruleContext.Criterias
.FilterByItems(dictionary, (c, kv) => c.Key == kv.Key && c.Value == kv.Value, true)
.GroupBy(c => c.RuleId)
.Where(g => g.Count() == dictionary.Count)
.Select(g => new
{
RuleId = g.Key,
});
var rules =
from r in _ruleContext.Rules
join m in matched on r.Id equals m.RuleId
select r;
var result = rules.ToArray();

Dapper with Mapping by code: Multi-Mapping with repeating column names

I'm trying to perform a simple query and the result data is almost all null.
I have this table structure
Table Registros
ID | Autonumeric
TareaM_Id | Numeric
Fecha | Date/Time
and Macro_tareas table
ID | Autonumeric
Nombre | Short Text
I have mapped the classes in C# like this:
[Table("Registros")]
public class Registro
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Fecha")]
public virtual DateTime Fecha { get; set; }
[Column("TareaM_Id")]
public virtual int TareaM_Id { get; set; }
public virtual MacroTarea MacroT { get; set; }
}
[Table("Macro_tarea")]
public class MacroTarea
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Nombre")]
public virtual string Nombre{ get; set; }
public virtual ICollection<Registro> Registros { get; set; }
}
This is the query i'm trying to use
string sql = #"SELECT reg.ID, mac.ID
FROM Registros as reg INNER JOIN Macro_tarea as mac on reg.TareaM_Id = mac.ID
WHERE Fecha = #Fecha";
using (IDbConnection db = new OleDbConnection(ConnectionString))
{
var result = db.Query<Registro,MacroTarea, Registro>(sql,
(reg,mac) =>
{
reg.MacroTarea = mac;
return reg;
}
,new { #Fecha = new DateTime(2019, 1, 4).Date }
, splitOn: "mac.ID")
.AsList();
}
I'm trying to only retrieve ids, but both id become null why is this happening?
The thing is, if I add Registros.Fecha and Macro_tarea.Nombre to the query, it got the value correctly. But id keep coming null.
Apparently the issue is happening only with ids. I suspect this issue is due to duplicate column names.
I'm working with Microsoft Access just in cast that matters.
My question is not similar to the possible duplicate because I have the classes defined as they should be mapped.
Renaming your database columns because your code cannot cope with the data is not a good idea. In the world of separation of concerns, why should your database care? There are good database reasons to name ID columns "Id", and you may not even have the option to change them.
There's another issue with Dapper mapping that renaming columns does not get around; repeated types. If you are trying to map to more than one instance of a class Dapper gets confused, and renaming columns won't work because you will rename both instances.
Here is the solution I have come up with. It's similar to a lot of examples that use a dictionary, except:
it can nest to as many levels as you like
can cope with Dappers 7 item limit
can cope with duplicates of the same class
can be reused e.g., for Get, GetCurrent and GetAll
In this example there is an Auction that has many Lots. Each Lot may have 1 or many Items. Items might be packs of Items. The Items are from a limited catalogue and we like relational data, so a Things table contains the details on each Item, like colour, size, etc. Here we are only getting a single Lot, but getting an Auction is the same with another level on top for Auction.
Parameter 1 - The SQL to get everything in one go
Parameter 2 - A Type array of each object we'll get back. For this reason it's best to order your SELECT to group the fields into the classes
Parameter 3 - Call the method we're about to write with the SQL result
Parameter 4 - Standard parameter array for the SQL. SQL Injection is bad, m'kay?
public async Task<List<Lot>> GetAll(int auctionId)
{
using (var connection = new SqlConnection(_appSettings.ConnectionString))
{
await connection.OpenAsync();
var result = new List<Lot>();
await connection.QueryAsync($#"
SELECT [Lot].*,
[Item].[Id],
[Item].[LotId],
[Item].[Notes],
itemDetails.[Id],
itemDetails.[ThingId],
itemDetails.[Colour],
itemDetails.[Size],
[SubItem].[Id],
[SubItem].[ItemId],
[SubItem].[Notes],
subItemDetails.[Id],
subItemDetails.[ThinId],
subItemDetails.[Colour],
subItemDetails.[Size]
FROM [Lot]
INNER JOIN [Item] ON [Item].[LotId] = [Lot].[Id]
LEFT JOIN [Thing] AS itemDetails ON itemDetails.[Id] = [Item].[ThingId]
LEFT JOIN [SubItem] ON [SubItem].[ItemId] = [Item].[Id]
LEFT JOIN [Thing] AS subItemDetails ON subItemDetails.[Id] = [SubItem].[ThingId]
WHERE [AuctionId] = #{nameof(auctionId)}
ORDER BY [Lot].[Id], [Item].[Id], [Expansion].[Id];",
new Type[] {
typeof(Lot),
typeof(Item),
typeof(Thing),
typeof(Expansion),
typeof(Thing)
},
MapResult(result),
new
{
AuctionId = auctionId
}
);
return result.ToList();
}
}
private Func<object[], Lot> MapResult(List<Lot> result)
{
return (obj) =>
{
Lot lot = (Lot)obj[0];
Item item = (Item)obj[1];
Thing itemDetails = (Thing)obj[2];
SubItem subItem = (SubItem)obj[3];
Thing subItemDetails = (Thing)obj[4];
if (lot != null)
{
if (result.Any(a => a.Id == lot.Id))
{
lot = result.First(a => a.Id == lot.Id);
}
else
{
result.Add(lot);
}
}
if (item != null)
{
if (lot.Items.Any(i => i.Id == item.Id))
{
item = lot.Items.First(i => i.Id == item.Id);
}
else
{
lot.Items.Add(item.FromThing(itemDetails));
}
}
if (expansion != null)
{
if (item.SubItems.Any(e => e.Id == subItem.Id) == false)
{
item.SubItems.Add(subItem.FromThing(subItemDetails));
}
}
return null;
};
}
MapResult is the meat of the code. It returns a Func with two types, the Type array we defined above and the return Type, and takes a List of the top level object.
I then map each item from the object array to another of it's actual type. This keeps the code easier to read, and enables properties and methods of the object to be accessed without issue.
Then it's a case of stepping down the hierarchy, checking at each step if one already exists with a matching id, and swapping the iterator to a reference to it if it does. This means that following code will add to the existing item.
In the particular case I've also added a FromThing function to allow easier combining of object properties.
As we discussed in comments, this is an issue due to duplicate column names in two tables. This is where the similar issue and solution could be found. But, it does not include "mapping by code" as you said. So it is not exact duplicate.
I suggest you change the names of ID fields in your tables to avoid colliding them. Of-course, you should also change the name of your POCO properties and mappings accordingly.
If you cannot change the column names in table, change the POCO property name, and use the column alias in SQL query to match those new property names.
I hope this helps you.
The problem was effectively the name of the properties.
I solved it using Custom Column Mapping to do it i got two possible solutions:
Without extensions
First, we define a Dictionary with the name of the column as key, and the name of the property as value
IDictionary<string, string> columnMaps = new Dictionary<string, string>()
{
{ "Macro_tarea.ID", "ID" },
{ "Registros.ID", "ID" }
};
Then, we define a delegate to obtain the PropertyInfo object of the property to which we intend to assign the alias of the previous dictionary
var mapper = new Func<Type, string, PropertyInfo>((type, columnName) =>
{
if (columnMaps.ContainsKey(columnName))
return type.GetProperty(columnMaps[columnName]);
else
return type.GetProperty(columnName);
});
Now, we define an object that implements the ITypeMap interface using CustomPropertyTypeMap implementation
ITypeMap MacroTareaMapper = new CustomPropertyTypeMap(typeof(Macro_tarea),
(type, columnName) => mapper(type, columnName));
ITypeMap RegistrosMapper = new CustomPropertyTypeMap(typeof(Registros),
(type, columnName) => mapper(type, columnName));
Then we register them
SqlMapper.SetTypeMap(typeof(Macro_tarea), MacroTareaMapper);
SqlMapper.SetTypeMap(typeof(Registros), RegistrosMapper);
Simpler solution with Dapper.FluentMap
It is implemented as follows:
We create a class that inherits from EntityMap<T> and using the Map method we define which column corresponds to each property. For example,
internal class Macro_tareaMap : EntityMap<Macro_tarea>
{
internal Macro_tareaMap()
{
//Mi propiedad ID esta asociada a la columna Macro_tarea.ID
Map(x => x.ID).ToColumn("Macro_tarea.ID");
}
}
Then just register it
FluentMapper.Initialize((config) =>
{
config.AddMap(new Macro_tareaMap());
});
Hope it helps another people!
Source: https://medium.com/dapper-net/custom-columns-mapping-1cd45dfd51d6

NHibernate select a list of objects with related child objects with QueryOver

I'm having trouble with something that is probably very simple.
In my database I have the following tables:
tblOrder
-----------------
Id
OrderStatusId
tblOrderStatus
-----------------
Id
Name
And I have made the following mappings in my project:
[Class(NameType = typeof(Order), Table = "tblOrder")
public class Order {
[Id(-2, Name = "Id")]
[Generator(-1, Class = "native")]
public virtual long Id { get; set; }
[ManyToOne]
public virtual OrderStatus Status { get; set; }
}
[Class(NameType = typeof(OrderStatus), Table = "tblOrderStatus")]
public class OrderStatus {
[Id(-2, Name = "Id")]
[Generator(-1, Class = "native")]
public virtual long Id { get; set; }
[Property]
public virtual string Name { get; set; }
}
The query should return a IList<OrderSummary>. I want the class OrderSummary to have a property Status where Status is an object with an Id and a Name property. This could be either with a KeyValuePair or of type OrderStatus (whichever is best and works). Fetching the orders is not a problem but adding the OrderStatus as an object with said properties is the part I'm having trouble with.
I also need to return the result of the query as JSON to the client.
OrderSummary should look like this:
public class OrderSummary {
public long Id { get; set; }
public OrderStatus Status { get; set; }
}
In my first version OrderSummary had separate properties for OrderStatusId and OrderStatusName. This works but I'm trying to avoid these separate properties.
I have also tried this with SelectSubQuery but this returns an error because it returns more than one field in a subquery.
----------------------------------- UPDATE -----------------------------
Following Fredy Treboux's advice I changed my query using Eager which result in the following query:
var query = session.QueryOver<OrderStatus>
.Fetch(o => o.Status).Eager
.JoinAlias(o => o.Status, () => statusAlias, JoinType.LeftOuterJoin);
The problem is, I found out, is not selecting the data but how to convert the retrieved Status and assign it to OrderSummary.Status? I have tried the following:
OrderSummary orderAlias = null;
query.SelectList(list => list
.Select(o => o.Id).WithAlias(() => orderAlias.Id)
.Select(() => statusAlias).WithAlias(() => orderAlias.Status)
).TransformUsing(Transformer.AliasToBean<OrderSummary>());
-------------------------------- ANSWER ----------------------------------
As I said in my last edit, the problem does not seem to be the actual selection of OrderStatus but returning it to the client. So I thought it was my lack of knowledge of NHibernate instead it was as simple as adding the [JsonObject] attribute to the OrderStatus class. How silly of me.
I have changed my query to the following:
Order orderAlias = null;
OrderSummary orderSummary = null;
OrderStatus statusAlias = null;
var query = session.QueryOver<Order>(() => orderAlias)
.JoinAlias(() => orderAlias.Status, () => statusAlias, JoinType.LeftOuterJoin);
query = query
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => orderAlias.Id).WithAlias(() => orderSummary.Id))
.Add(Projections.Property(() => orderAlias.Status).WithAlias(() => orderSummary.Status)
);
Result = query.TransformUsing(Tranformers.AliasToBean<OrderSummary>())
.List<OrderSummary>()
.ToList();
I'm afraid that currently it's not possible. I guess that Nhibernate transformers are not able to construct nested complex properties.
You can return list of tuples and then cast it manually to your entity:
OrderStatus statusAlias = null;
var tuples = Session.QueryOver<Order>()
.JoinQueryOver(x => x.Status, () => statusAlias)
.SelectList(list => list
.Select(x => x.Id)
.Select(x => statusAlias.Id)
.Select(x => statusAlias.Name))
.List<object[]>();
var result = tuples.Select(Convert);
private OrderSummary Convert(object[] item) {
return new OrderSummary
{
Id = (long)item[0],
OrderStatus = new OrderStatus { Id = (long)item[1], Name = (string)item[2] }
};
}
Also if you don't bother about performance much it's possible to fetch a list of you Orders and convert it to OrderSummary. You can do it by simply define casting operator or using some tool like AutoMapper or ExpressMapper.
Sorry I didn't see your comment asking for an example before.
I'm going to leave some code explaining the approach I mentioned, although it was already given as an alternative in the other response and I believe it's the easiest way to go (not using transformers at all):
string GetOrderSummaries()
{
// First, you just query the orders and eager fetch the status.
// The eager fetch is just to avoid a Select N+1 when traversing the returned list.
// With that, we make sure we will execute only one query (it will be a join).
var query = session.QueryOver<Order>()
.Fetch(o => o.Status).Eager;
// This executes your query and creates a list of orders.
var orders = query.List();
// We map these orders to DTOs, here I'm doing it manually.
// Ideally, have one DTO for Order (OrderSummary) and one for OrderStatus (OrderSummaryStatus).
// As mentioned by the other commenter, you can use (for example) AutoMapper to take care of this for you:
var orderSummaries = orders.Select(order => new OrderSummary
{
Id = order.Id,
Status = new OrderSummaryStatus
{
Id = order.Status.Id,
Name = order.Status.Name
}
}).ToList();
// Yes, it is true that this implied that we not only materialized the entities, but then went over the list a second time.
// In most cases I bet this performance implication is negligible (I imagine serializing to Json will possibly be slower than that).
// And code is more terse and possibly more resilient.
// We serialize the DTOs to Json with, for example, Json.NET
var orderSummariesJson = JsonConvert.SerializeObject(orderSummaries);
return orderSummariesJson;
}
Useful links:
AutoMapper: http://automapper.org/
Json.NET: http://www.newtonsoft.com/json

C# aggregate data from mulitple columns taken from a resultset of an RAW SQL query with Entity Framework6 and add those data to Lists in a ViewModel

I want to process the data from my database-query using raw SQL in Entity Framework 6 as follows and need a best practice by the use of native functions of C# and LINQ:
PICTURE 1: Resultset taken from database
I have created a class for the resultset above, it looks like that:
public class ProjectQueryModel {
public int Project { get; set; }
public string Projectname { get; set; }
public int RoomId { get; set; }
public string RoomName { get; set; }
public int? EmployeeId { get; set; }
public string EmployeeName { get; set; }
public int? QualificationId { get; set; }
public string QualificationName { get; set; }
public int? QualificationLevel { get; set; }
}
To this point the query works and I got all my data from it stored in a List of type ProjectQueryModel. Now I want to add this data to my ViewModel and don't know how to use the functions C# offers me to process the data of resultsets. How can I achieve the following by saving every entity of type ProjectViewModel in a List, whose objects have the following structure:
PICTURE 2: data organisation in ViewModel
An example dataset for project 1 in the target list should look like this:
ProjectId = 1
Projectname = T1
RoomId = 1
RoomName = Delta Room
======================
Employees *(Attribute of type List <ProjectEmployeesVM> )*
[0].EmployeeId = 2
[0].EmployeeName = Mee
[0].EmployeeQualifications *(Attribute of type List<EmployeeQualificationsVM)*
[0].EmployeeQualifications[0].QualificationId = 1
[0].EmployeeQualifications[0].QualificationName = Programmer
[0].EmployeeQualifications[0].QualificationLevel = 3
...any other qualification of the employee
[1].EmployeeId = 2
[1].EmployeeName = Mee
[1].EmployeeQualifications
[1].EmployeeQualifications[0]
...Any other employee in this project and all of his qualifications
What I also want to achieve is to save a empty list in case the project has no employees, because the resultset is achieved by the use of LEFT OUTER JOINS. For the qualifications it is not necessary, because every employee has at least one qualification.
VERY BIG THANKS in advance
I'm supposing you have a constructor in every class involved that takes all the properties as arguments.
Here's how i would do it:
List<ProjectQueryModel> queryResult = ...;
List<ProyectViewModel> views = queryResult
// Take all the rows that belong to one proyect
.GroupBy(m => m.Proyect)
// Convert every group into a ProyectViewModel
// First use Select to Map every Group into a new Proyect using a function that takes a group of rows and return a Proyect
// Then we use Aggregate inside that mapping function to collapse the entire group of rows into a single ProyectViewModel
// We'll need a contructor in ProyectViewModel that gives us a completly empty instance
// Aggregate takes a starting point, and a function that takes that starting point, and passes it every element of the IEnumerable we're using. The return value of that function is the "new starting point".
// Using this we'll build the Proyect from every row.
.Select(g => g.Aggregate(new ProyectViewModel(), (pvm, nxtRow) => {
// Check if we haven't initialized the instance, and do so.
if (pvm.ProyectId == null) pvm.ProyectId = nxtRow.Proyect;
if (pvm.ProyectName == null) pvm.ProyectName = nxtRow.ProyectName;
if (pvm.RoomId == null) pvm.RoomId = nxtRow.RoomId;
if (pvm.RoomName == null) pvm.RoomName = nxtRow.RoomName;
if (pvm.Employees == null) pvm.Employees = new List<ProyectEmployeeViewModel>();
// If the row has an employee
if (nxtRow.EmployeeId.HasValue) {
// If the Employee is not yet on the Proyect add it
if (!pvm.Employees.Any(e => e.EmployeeId == nxtRow.EmployeeId))
{
// This constructor should create the empty List of Qualifications
pvm.Employees.Add(new ProyectEmployeeViewModel(nxtRow.EmployeeId.Value, nxtRow.EmployeeName);
}
// If the row has a qualification
if (nxtRow.QualificationId.HasValue)
{
// Find it's employee
pvm.Employees.First(e => e.EmployeeId == nxtRow.EmployeeId)
// Add the current row's qualification to the employee
.Qualifications.Add(new EmployeeQualificationsViewModel(nxtRow.QualificationId.Value, nxtRow.QualificationName, nxtRow.QualificationLevel.Value));
}
}
// Return the Proyect with the changes we've made so we keep building it
return pvm;
})).ToList();
LINQ is quite a beauty isn't it?
There might be errors, but use this as a starting point.
Start by making sure that your database has the right foreign key constraints between your tables, then update your model. This will automatically create the correct navigation properties. I've assumed they will be called Employees and Qualifications, but change as appropriate.
Then your query just becomes:
var result=db.Projects
.Include(p=>p.Employees)
.Include(p=>p.Employees.Select(e=>e.Qualifications))
.Where(p=>p.id==1)
.AsEnumerable(); // or .ToList() if you prefer
Then just pass IEnumerable<Project> to your view (or just Project if your view will always only get 1 Project -- in that case, just end the query with .First() instead of .AsEnumerable()) . Unless of course you like creating ViewModels, but I'm guessing you don't and this isn't a project that needs the added complexity or abstractions.
The above code assumes you have the following tables:
Project (int Id, varchar(50) Name, int RoomId)
Room (int Id, int Name)
Employee (int Id, varchar(50) Name)
Qualification (int Id,varchar(50) Name, int Level)
Cross Reference tables:
ProjectEmployees (int ProjectId, int EmployeeId)
EmployeeQualifications (int EmployeeId, int QualificationId)
Foreign Keys:
Project.RoomId -> Room.Id
ProjectEmployees.ProjectId -> Project.Id
ProjectEmployees.EmployeeId -> Employee.Id
EmployeeQualifications.EmployeeId -> Employee.Id
EmployeeQualifications.QualificationId -> Qualification.Id

How do I return data from joined tables through subsonic's objects?

I'm using ActiveRecord on Subsonic 3 and I effectively want to do this:
select * from foo
left outer join bar on bar.Id = foo.barId
where foo.someProperty = 2
I've written a stored procedure to fetch the data but Subsonic has only created objects to hold the columns from foo and bar.
What's the best way of returning the data into a single object so I can just bind it. Ideally I want it to be in a list<> but without writing my own class, there doesn't seem to be a way provided by subsonic.
You have a couple options here...
You could create a database view that does your join, and have SubSonic generate a data type for your view, then your select would be just like selecting from any other table.
Alternatively, you could use a Linq expression to do the join into an anonymous or dynamic type (if you are using .net 4) For example:
public List<dynamic> LoadData(int id)
{
var data = from f in db.Foo
from b in db.Bar.Where(x => x.Id == f.BarId).DefaultIfEmpty()
where f.SomeProperty == id
select new
{
SomeProperty = f.Something,
AnotherProperty = b.SomethingElse
};
return data.Cast<dynamic>().ToList();
}
Of course another alternative is to do the Linq expression above, but define your own class to hold the returned data, and select into it.
public class MyData
{
public string SomeProperty { get; set; }
public string AnotherProperty { get; set; }
}
public List<MyData> LoadData(int id)
{
var data = from f in db.Foo
from b in db.Bar.Where(x => x.Id == f.BarId).DefaultIfEmpty()
where f.SomeProperty == id
select new MyData()
{
SomeProperty = f.Something,
AnotherProperty = b.SomethingElse
};
return data.ToList();
}

Categories

Resources