Lambda Select Top 1 item on underlying List - c#

I have 2 tables with relation
Customer
Id, Nbr, Name
Assignments
Id, CustomerId, Location, AssigmentTime
There is a relation on Customer.Id = Assigments.CustomerId
Every Customer can have lot of assignments, but I am only interested in the last Assignment according to DateTime field AssigmentTime
In SQL it should be a query like:
Select Top 1 From Customer c
Inner Join Assigments a On c.Id = a.CustomerId
Where c.Nbr = 1234
Order By AssigmentTime Desc
I have a problem to construct proper Lambda query.
This code works, but it’s not very effective:
var customerNbr = 1234:
var cst = context.Customers.FirstOrDefault(x => x.Nbr == customerNbr);
if (cst != null && cst. Assigments.Count > 1)
{
cst. Assigments = new List<Assigments>
{
cst.Assigments.OrderByDescending(x => x.AssigmentTime).FirstOrDefault()
};
}
How can I get Customer with just 1 top Assigments in Customer.Assigments List property?

For example:
var lastAssignment = customers.Where(x => x.Nbr == customerNbr)
.SelectMany(x => x.Assignments)
.OrderByDescending(x => x.AssignTime)
.FirstOrDefault();

If you have set-up your entity framework according to the proper coding conventions you'll have designed the one-to-many relation as follows:
class Customer
{
public int Id {get; set;} // primary key
// a Customer has zero or more Assignments
public virtual ICollection<Assignment> Assignments {get; set;}
public int Nbr {get; set;}
... // other properties
}
class Assignment
{
public int Id {get; set;} // primary key
// every Assignment belongs to one Customer via foreign key
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
public DateTime AssignmentTime {get; set;}
... // other properties
}
public MyDbContext : DbContext
{
public DbSet<Customer> Customers {get; set;}
public DbSet<Assignment> Assignments {get; set;}
}
If you've set-up the one-to-many like this, then this is all entity framework needs to know that you designed a one-to-many relationship. If you didn't want to follow the naming conventions, you probably have used fluent API or attributes to configure the one-to-many.
Get the Customer with Nbr = 1234 with his last (newest) Assignment:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new
{
// select the customer properties you will use, for instance
CustomerId = customer.Id,
CustomerName = customer.Name,
// you only want the newest assignment:
NewestAssignment = customer.Assignments
.OrderByDescending(assignment => assignment.AssignmentTime)
.Select(assignment => new
{ // take only the Assignment properties you will use:
Location = assignment.Location,
AssignmentTime = assignment.AssignmentTime,
}
.FirstOrDefault(),
});
}
}
If you are certain there is at utmost one customer with Nbr = 1234, you can end with SingleOrDefault; otherwise your result will be the sequence of Customers with this Nbr.
Each customer will only have the customer properties you will use, and the properties of the newest Assignment you will use. Efficient!

Thank you for your suggestion Harald. I was on to same thig, but I found anonymous object to be a bit bloated. In my case I use EF.Reverse.POCO Generator, so every object is strictly mapped to DB. Customer and Assignments are in reality something else – tables with lot of columns. I can’t have anonymous object as a return from this function.
I could still do something like this:
using (var dbContext = new MyDbContext())
{
var result = dbContext.Customers
.Where(customer => customer.Nbr == 1234)
.Select(customer => new Customer
{
// select the customer properties you will use, for instance
Id = customer.Id,
Nbr = customer.Nbr,
Name = customer.Name,
//…and lot of other property mapping
// you only want the newest assignment:
Assignments = new Collection<Assignments>
{
customer.Assignments.OrderByDescending(assignment => assignment.AssignmentTime)
.FirstOrDefault()
}
});
}
}
The anonymous Customer generations will result in lot of property mapping. That’s the minor issue.
Even if I skip Assignments property, this solution with typed object in Select generates an exception inside the result:
Message = "The entity or complex type 'MyNamespace.Customer' cannot be constructed in a LINQ to Entities query."
If I use anonymous object the same code works fine, but as I wrote above – I need typed objects as return.

Related

I'm pretty new to linq lambda. I have a MySQL query.which I need to convert to a lambda query

This is my SQL query:
SELECT TOP 1000
s.id, s.[index], s.staffini,
MIN(accessdate) AS [In],
MAX(accessdate) AS [Out],
sa.mode, sa.daytype
FROM
staffattendances AS sa
INNER JOIN
staffs AS s ON sa.staffid = s.id
GROUP BY
staffid, CAST(accessdate AS DATE), s.staffname,
sa.mode, sa.daytype, s.[index], s.staffini, s.id
How to convert this to a Linq lambda query?
var tmp = Context.staffAttendances
.Include(t => t.staff)
.GroupBy(s => new
{
s.StaffId, s.,
s.AccessDate,
s.staff.StaffName,
s.Mode, s.Daytype,
s.staff.index, s.staff.Id
})
.Select(x => new staffattdto
{
index = x.Key.index,
StaffIni = x.Key.StaffName,
In = x.Max(t => t.AccessDate),
Out = x.Max(t => t.AccessDate),
mode = x.Key.Mode,
daytype = x.Key.Daytype,
})
.OrderByDescending(t => t.In);
I personally prefer using linq query syntax for these kind of queries.
Sample below:
var results = (from o in Organizations
join m in Members on o.OrganizationId equals m.OrganizationId
group o by new { o.OrganizationId, m.MemberId } into g
select new
{
g.Key.OrganizationId,
g.Key.MemberId
})
.Take(1000);
Alas you forgot to mention your classes and the relations between the tables, so I have to make a guess.
So you have Staffs and StaffAttendances. It seems to me that there is a one-to-many relation between Staffs and StaffAttendances: every Staff has zero or more StaffAttendances; every StaffAttendance belongs to exactly one Staff, namely the Staff that foreign key StaffAttendance.StaffId refers to.
You will have classes similar to the following:
class Staff
{
public int Id {get; set;}
public string Name {get; set;}
public string Ini {get; set;}
...
// Every Staff has zero or more StaffAttendances (one-to-many)
public virtual ICollection<StaffAttendance> StaffAttendances {get; set;}
}
class StaffAttendance
{
public int Id {get; set;}
public string Mode {get; set;}
public int DayType {get; set;}
...
// every StaffAttendance belongs to exactly one Staff, using foreign key
public int StaffId {get; set;}
public virtual Staff Staff {get; set;}
}
I'm trying to figure out what your query does. It seems something like: "From every Staf, with its zero or more StaffAttendances give me the Id, the StaffName, (maybe some more Staff properties), and from all its StaffAttendances with the same Mode and DayType give me the minimum and maximum StafAttendance AccessDate"
In entity framework always use Select to query data and select only the properties that you actually plan to use. Only use Include if you plan to change the included data.
The reason for this is that Include will fetch the complete object, inclusive the properties that you won't use. If you have a database with Schools and Students, then every Student will have a foreign key to the School that he attends. Every Student on School [10] will have a foreign key SchoolId with a value [10].
If you use Include to query "School [10] with all its 2000 students" you will be transferring the value 10 more than 2000 times, while you already know this value.
Use the virtual ICollection
I think your query will be something like:
var result = dbContext.Staffs
// only if you don't want all Staffs:
.Where(staff => ...)
.Select(staff => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.StaffAttendances.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// parameter resultSelector, for every [Mode, DayType] combination,
// and all StaffAttendances that have this [Mode, DayType] combination
// make one new:
(modeDayType, staffAttendancesWithThisModeDayType) => new
{
Mode = modeDayType.Mode,
DayType = modeDayType.DayType,
In = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Min(),
Out = staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate)
.Max(),
},
});
Entity framework knows the relations between your tables, and translates the usage of your virtual ICollection into the proper GroupJoin.
It seems to me that you want some more properties. Since I use entity framework my SQL is a bit rusty, but I guess you'll get the gist.
I think that you don't have a Min / Max for DateTime. If you don't, you'll have to convert them to Ticks and back:
In = new DateTime(staffAttendancesWithThisModeDayType
.Select(staffAttendance => staffAttendance.AccessDate.Ticks)
.Min()),
If you are using this more often, consider to create an Extension method for this, they are one-liners, and because you reuse them it might save you a lot of time unit testing them:
public static DateTime MinAccessDate(this IQueryable<StaffAttendance> attendances)
{
return attendances.Select(attendance => attendance.AccessDate).Min();
}
public static DateTime Min(this IQueryable<DateTime> dates)
{
return new DateTime(dates.Select(date => date.Ticks).Min());
}
Usage:
In = staffAttendancesWithThisModeDayType.MinAccessDate(),
Out = staffAttendancesWithThisModeDayType.MaxAccessDate(),
In my opinion, using the virtual ICollection in a one-to-many relationship is the method that is by far the easiest to understand. Unit tests will also be easier, because your input data can come from Dictionaries or Lists instead of from real databases.
Do the GroupJoin yourself
Some people use a version of entity framework that doesn't understand the virtual ICollection, or they prefer to do the GroupJoin to fetch the Staff with their Attendances themselves. The second halve of the query will be similar:
var result = dbContext.Staffs.GroupJoin(dbContext.StaffAttendances,
staff => staff.Id, // from every Staff take the primary key
attendance => attendance.StafId, // from every StaffAttendance take the foreign key
// parameter resultSelector: for every Staff, and its zero or more StaffAttendances
// make one new
(staff, attendancesOfThisStaff) => new
{
Id = staff.Id,
Ini = staff.StaffIni,
Attendance = staff.attendancesOfThisStaff.GroupBy(attendance => new
{
Mode = attendance.Mode,
DayType = attendance.DayType,
},
// etc. see above
});

Linq join does not include data from child table in Entity Framework

I am trying to join few tables and return all data from all tables in the list.
Here is the query I tried
List<TblCrrequests> mycr = (from ep in _context.TblCrrequests
join e in _context.TblCrExternalBudget on ep.CrId equals e.CrId
join i in _context.TblCrInternalbudget on ep.CrId equals i.CrId
join t in _context.TblExternalBudgetTypes on e.TypeId equals t.TypeId
where ep.ProjectCode == pcode
select ep)
.OrderByDescending(d => d.RequestedOn)
.Take(8).ToList<TblCrrequests>();
But on executing in this list variable, it only contain data for the main table only. Showing the child table array as empty. How to modify the query to get all those details from the main and child tables as well into the list variable. I am very new to linq..
I tried select ep,e,i,t which didn't work. Also if I don't need all columns from one table, is that possible?
Here is my class defenitions
public partial class TblCrrequests
{
public TblCrrequests()
{
TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
}
public int CrId { get; set; }
public string ProjectCode { get; set; }
public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}
public partial class TblCrExternalBudget
{
public int? CrId { get; set; }
public int? TypeId { get; set; }
public virtual TblCrrequests Cr { get; set; }
public virtual TblExternalBudgetTypes Type { get; set; }
}
public partial class TblExternalBudgetTypes
{
public TblExternalBudgetTypes()
{
TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
}
public int TypeId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}
Use Include() method on a related property you would like to load too. Something like this:
// in this case its assuming that
var books = (from b in db.books.Include(p => p.Authors)
where <your filter criteria>
select b).ToList();
The Include() method instructs it to load book's Authors with it as well.
Alas, you forgot to tell us the classes that you have. You also didn't inform us about the relations between the tables. I'll have to guess from your usage. Next question you ask consider to provide this information.
It seems that you have a table with Requests, and a table InternalBudgets. There seems to be a one-to-many relation between Requests and InternalBudgets: every Request has zero or more InternalBudgets, every InternalBudget belongs to exactly one Request, namely the one that the foreign key refers to.
There seems to be a similar one-to-many between Requests and ExternalBudgets.
Finally there is also a one-to-many between ExternalBudgets and ExternalBudgetTypes. Personally I would expect a many-to-many relation: every ExternalBudgetType is used by zero or more ExternalBudgets. This doesn't change the answer drastically.
If you've followed the entity framework conventions, you'll have classes similar to the following:
class Request
{
public int Id {get; set;}
...
// Every Request has zero or more InternalBudgets (one-to-many)
public virtual ICollection<InternalBudget> InternalBudgets {get; set;}
// Every Request has zero or more ExternalBudgets (one-to-many)
public virtual ICollection<ExternalBudged> ExternalBudgets {get; set;}
}
The Internal and External budgets:
class InternalBudget
{
public int Id {get; set;}
...
// Every InternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
}
class ExternalBudget
{
public int Id {get; set;}
...
// Every ExternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
// Every ExternalBudget has zero or more ExternalBudgetTypess (one-to-many)
public virtual ICollection<ExternalBudgetType> ExternalBudgetTypes {get; set;}
}
Finally the ExternalBudgetTypes:
class ExternalBudgetType
{
public int Id {get; set;}
...
// Every ExternalBudgetType belongs to exactly one ExternalBudget, using foreign key
public int ExternalBudgetId{get; set;}
public virtual ExternalBudget ExternalBudget {get; set;}
}
Because you stuck to the conventions, this is all that entity framework needs to detect your tables, the relations between the tables and the primary and foreign keys
In entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
The foreign key is a column in the table. Hence it is non-virtual. The object that the foreign key refers to is not part of the table, hence if is virtual.
The "Many" side in xxx-to-many relations should be implemented with ICollection<...>, not with IList<...>. Ilist provide functionality that is undefined in databases:
Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];
Can you say which internalBudget has list index [3]?
Better not provide functionality that is not defined. ICollection<...> give you the possibility to Add / Delete / Count, all functionalities that have a defined meaning in a database.
One final tip: in your identifiers use plural nouns to refer to sequences; use singular nouns to refer to elements in these sequences. That makes LINQ statements easier to understand.
Back to your question
Requirement Given a projectCode, give me the requests that have this projectCode, with several properties of their Internal and External Budgets
Easy method: use the virtual ICollections
var projectCode = ...
var result = dbContext.Requests
// Keep only the Requests that have projectCode
.Where (request => request.ProjectCode == projectCode)
// order the remaining Requests by descending RequestedOn date
.OrderByDescending(request => request.RequestedOn)
// Select the properties that you want:
.Select(request => new
{
// Select only the Request properties that you plan to use
Id = request.Id,
Name = request.Name,
...
// Internal budgets of this Request
InternalBudgets = request.InternalBudgets.Select(budget => new
{
// Again, only the properties that you plan to use
Id = budget.Id,
...
// not Needed, you know the value
// RequestId = budget.RequestId,
})
.ToList(),
// External budgets of this Request
ExternalBudgets = request.ExternalBudgets.Select(budget => new
{
...
ExternalBudgetTypes = budget.ExternalBudgetTypes
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
In words: from all Requests, keep only those Request that have a value of property ProjectCode equal to projectCode. Order the remaining Requests by descending value of property RequestedOn. Finally Select several properties of the Request, its internal budgets and its external budgets.
Entity framework knows the relations between the tables, and knows how to translate the use of your virtual properties in the correct (Group-)join.
Note: the result differs slightly from your solution. Your solution would give:
Request InternalBudget (for simplicity: leave out Externalbudget / type
01 10
02 12
04 11
01 14
01 15
02 13
My Solution gives:
Request 01 with its InternalBudgets {10, 14, 15}
Request 02 with its InternalBudgets {12, 13}
Request 05 without any InternalBudgets
Request 04 with its InternalBudget {11}
IMHO this is much more intuitive and more efficient: the properties of each Request items are transferred only once.
Note that you will also get the Requests that have no Internal/External budgets or types.
If you don't want them, use a Where(... => ...Count != 0) to filter them out.
If you don't want "Items with their Sub-items" (which is in fact a GroupJoin), but a standard join, use SelectMany to flatten the result
Do the Join yourself
Some people don't like to use the virtual ICollection<...>. They prefer to do the (Group-)Joins themselves. In method syntax Joins with more than two tables look horrible, luckily you don't have to do a Join.
var result = dbContext.Requests
.Where (request => request.ProjectCode == projectCode)
.OrderByDescending(request => request.RequestedOn)
.Select(request => new
{
// Request properties:
Id = request.Id,
Name = request.Name,
...
// Internal budgets:
InternalBudgets = dbContext.InternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
})
.ToList(),
// External budgets:
ExternalBudgets = dbContext.ExternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
BudgetTypes = dbContext.BudgetTypes
.Where(budgetType => budgetType.ExternalBudgetId == budget.Id)
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
I'm not sure if you can convince your project leader that this method is better readable, more reusable, easier to test and change than the other one with the virtual ICollections.

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

creating new object in select based on linked table in linq

I have a table that has a FK to another table. I'm using a "view model" class that I want to return instead of the actual EF class. I know I can join the 2 tables and filter on the first table and then make a new view model in the select part based on the fields that I want, but can I do this without the join and instead use the linked table (via FK) in the select to create the view model instance?
ie. this works:
return (from a in context.Articles
join t in context.Topics on a.TopicID equals t.Id
where t.Name == topic
select new ArticleViewModel { Title = a.Title, Introduction = a.Introduction, Content = a.Content }).ToList();
Can I somehow create my ArticleViewModel with the Topics.Article link in a short and clean fashion:
return (from t in context.Topics
where t.Name == topic
select /*how can I create ArticleViewModel from t.Articles?*/ t.Articles).ToList();
Not HQL but Linq, I don't think you can do a select many in HQL, so your left with doing the join, or a sub query...
context.Topics.Where(x => x.Name.Equals(topic))
.SelectMany(y => y.Articals)
.Select(z => new ArticalViewModel { Title = z.Title , etc.. });
It seems you have a DbContext
with a DbSet of Articles. Each Article has a Topic.
with a DbSet of Topics. Each Topic has a collection of Articles that belongs to the topic.
And you want all articles that belong to a topic with the value of variable topic.
If you use entity framework and you have a one to many relation like you have in your Topic class, the "many" part has a foreign key to the "one" and the "one" part has a virtual ICollection of the "many" part
An Article belongs to exactly one Topic. An article "has" a Topic.
A topic has a collection of articles. Each item of the collection belongs to the topic.
This is designed as follows:
public class Article
{
public int Id {get; set;} // by convention will become primary key
public int TopicId {get; set;} // by convertion will become foreign key to Topic
public Topic Topic {get; set;} // an article "has" a Topic
public string Title {get; set;}
public string Introduction {get; set;}
// etc.
}
public class Topic
{
public int Id {get; set;} // by convention primary key
public virtual ICollection<Article> Articles {get; set;}
...
// other Topic properties
}
public class MyDbContext : DbContext
{
public DbSet<Topic> Topics {get; set;}
public DbSet<Article> Articles {get; set;}
}
The following Linq statement will get your sequence of ArticleViewModel items
(I put them in small steps so it's clear what happens
using (var context = new MyDbContext(...))
{
IQueryable<Article> articlesOfTopicsWithTopicName = context.Topics
.Where(topc=> topc.Name == myTopicName)
.SelectMany(topic => topic.Articles);
Whenever you have a sequence of type A and each A has a sequence of type B, and you want all B objects, you use SelectMany.
IQueryable<ArticleViewModel> requestedItems =
articlesOfTopicsWithTopicName.Select(article =>
new ArticleViewModel()
{
Title = article.Title,
Introduction = article.Introduction,
...
}
// delayed execution, nothing has been performed yet
return requestedItems.ToList();
// execute and return the list
}
If you use the database profiler, you will see that that selectMany will do a join on the foreign key in articles with the primary key in topic

LINQ to SQL separation and reusability of data structures

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.

Categories

Resources