Complex EntityFramework query with multiple tables and many to many relationship - c#

I'm having a heck of a time getting Entity Framework to do what I want. I'm writing a feed aggregator so I can add multiple rss feeds to a "FeedList" that will group all the individual podcasts and order by pubDate.
Classes (all but FeedListFeed have identity column called Id):
Feed (Id is identity primary key, has List Items property)
FeedItem (Id is identity primary key, as int FeedId and Feed Feed properties)
FeedList (FeedListName is string and List Feeds property)
FeedListFeed (many-to-many linking table, FeedListId and FeedId properties)
These mappings seems to work:
modelBuilder.Entity<FeedListFeed>().HasKey(x => x.FeedId).HasKey(x => x.FeedListId);
modelBuilder.Entity<FeedList>()
.HasMany(fl => fl.Feeds).WithMany(f => f.FeedLists)
.Map(t => t.MapLeftKey("FeedListId")
.MapRightKey("FeedId")
.ToTable("FeedListFeeds"));
Now what I want to do is get the latest 20 FeedListItem entries for Feeds in a FeedList given the FeedListName. I've come up with this, but is there a better way to do it? Will the query actually expand all the items, or will it be smart enough to do it on the SQL side?
var query =
from fl in ctx.FeedLists.Include("Feeds").Include("FeedItems")
from f in fl.Feeds
from fi in f.Items
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;
List<FeedItem> items = query.Take(20).ToList();
If I try to link the tables manually using the Id columns, I get the error Invalid object name 'dbo.FeedListFeeds1'. If I took out the Lists that link the tables to each other would this help? Is there some other mapping that let this work?
var query =
from fl in ctx.FeedLists
join flf in ctx.FeedListFeeds on fl.Id equals flf.FeedListId
join fi in ctx.FeedItems on flf.FeedId equals fi.FeedId
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;

Remove this line from your mapping ...
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => x.FeedId)
.HasKey(x => x.FeedListId);
... because it has 2 issues:
1) If you want a composite key you must not chain HasKey but create the key via an anonymous type:
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => new { x.FeedId, x.FeedListId });
2) (more important) This line lets EF consider FeedListFeed as an entity which it isn't in your model. The result is that EF creates a separate table for it with the name FeedListFeeds1 because FeedListFeeds is reserved as table name in your many-to-many mapping (.ToTable("FeedListFeeds")). For many-to-many mapping you don't need to create a class for the linking table. The linking table is managed by EF internally.
Edit
You can then also remove the FeedListFeed class completely of course.
For the query I would try then:
var query = from fi in ctx.FeedItems
where fi.Feed.FeedLists.Any(fl => fl.FeedListName == id)
orderby fi.PubDate descending
select fi;
I think you have all the necessary navigation properties in your model classes so that this query should be possible.

Related

Use table name and column name as dynamic in linq using entity [duplicate]

I want to get list of records from an entity model (I'm using EF version 5) with a particular accountID. I'm being supplied with the tableName string (this has to be dynamic) and the accountID. I'm trying the following 2 methods but none of them is working (giving me errors on the IQueryable object 'table':
PropertyInfo info = _db.GetType().GetProperty(tableName);
IQueryable table = info.GetValue(_db, null) as IQueryable;
var query = table.Where(t => t.AccountID == accID)
.Select(t => t);
List <object> recList = ( from records in table
where records.AccountID == accID
select records).ToList<object>();
The var query = table.Where(....).Select(...) is the correct move as it allows reflection for the query builder at runtime. However, t.AccountID is an error because of the type of t remains unknown.
I've previously used a similar approach in LINQ to SQL, using System.Linq.Expressions.Expression, e.g.:
// NOT TESTED
var table=context.GetTable(dynamicTableName);
var theT=table.Experssion; // actually, I forget. DynamicExpression or MemberBinding? or
var theField=Expression.Field(theT, "AccountID"); // or dynamic name
var query=table.Where(Expression.Equal(theField, accID);
var recList=query.ToList<object>();
If your object has a common interface there is a simpler syntax:
IQueryable<MyInterface> table = context.GetTable("table") as IQueryable<MyInterface>;
var recList=from r in table
where table.AccountID == ac // if your AccountID is on MyInterface
select table;
If you only have a few tables to support, you could do this as well:
IQueryable<MyInterface> table;
if("table1"==tableName)
table=_db.table1
elseif("table2"==tableName)
table=_db.table2
elseif("table3"==tableName)
table=_db.table3
else
throw exception
I built a DynamicRepository for a project I am working on. It uses generic methods exposed through EF along with dynamic linq. It might be helpful to look at that source code here:
https://dynamicmvc.codeplex.com/SourceControl/latest#DynamicMVC/DynamicMVC/Data/DynamicRepository.cs
You can query the entity framework metadata workspace to get the type for a given table name. This link might help:
Get Tables and Relationships

Need to Include() related entities but no option to do so

I'm not sure how else to word the title of this question so let me explain.
I have a need to select most of one entity type from my database, using .Include to select it's related entities, but at the same time to only select the entities where the entity identifier is equal to one of the IDs in a string array.
My code as follows:
List<TSRCategory> electives = new List<TSRCategory>();
foreach (var i in client.Electives.Split('&'))
{
int id = Int32.Parse(i);
electives.Add(db.TSRCategories.Find(id));
}
This correctly selects the TSRCategories that are part of the Electives list of IDs, but does not include the related entities. I was using this code:
TSRCategories = db.TSRCategories.Include("Competencies.CompetencySkills").ToList();
but this does not select only the chosen Electives. What I am ideally looking for is something like this:
List<TSRCategory> electives = new List<TSRCategory>();
foreach (var i in client.Electives.Split('&'))
{
int id = Int32.Parse(i);
electives.Add(db.TSRCategories.Find(id));
}
TSRCategories = electives.Include("Competencies.CompetencySkills").ToList();
But of course this can't be done for whatever reason (I don't actually know what to search for online in terms of why this can't be done!). Electives is a string with the & as a delimiter to separate the IDs into an array. TSRCategories contains Competencies which contains CompetencySkills. Is there a way to actually do this efficiently and in few lines?
You will find that fetching the associated ids one by one will result in poor query performance. You can fetch them all in one go by first projecting a list of all the needed ids (I've assumed the key name ElectiveId here):
var electiveIds = client.Electives.Split('&')
.Select(i => Int32.Parse(i))
.ToArray();
var electives = db.TSRCategories
.Include(t => t.Competencies.Select(c => c.CompetencySkills))
.Where(tsr => electiveIds.Contains(tsr.ElectiveId))
.ToList();
But one thing to mention is that the storage of your ids in a single string field joined by a delimiter violates database normalization. Instead, you should create a new junction table, e.g. ClientElectives which link the Electives associated with a Client in normalized fashion (ClientId, ElectiveId). This will also simplify your EF retrieval code.
Edit
According to the examples in the documentation, I should be using .Select for depth specification of the eager loading (not .SelectMany or other extension methods).
Try to use this extensions method:
using System.Data.Entity;
from x in db.Z.Include(x => x.Competencies)
.Include(x => x.Competencies.CompetencySkills)
select a.b.c;
To search by the given list of ids:
int[] ids = new int[0]; // or List<int>
from x in db.Z
where ids.Contains(x.Id)
select a.b.c;

LINQ Query - Only get Order and MAX Date from Child Collection

I'm trying to get a list that displays 2 values in a label from a parent and child (1-*) entity collection model.
I have 3 entities:
[Customer]: CustomerId, Name, Address, ...
[Order]: OrderId, OrderDate, EmployeeId, Total, ...
[OrderStatus]: OrderStatusId, StatusLevel, StatusDate, ...
A Customer can have MANY Order, which in turn an Order can have MANY OrderStatus, i.e.
[Customer] 1--* [Order] 1--* [OrderStatus]
Given a CustomerId, I want to get all of the Orders (just OrderId) and the LATEST (MAX?) OrderStatus.StatusDate for that Order.
I've tried a couple of attempts, but can seem to get the results I want.
private IQueryable<Customer> GetOrderData(string customerId)
{
var ordersWithLatestStatusDate = Context.Customers
// Note: I am not sure if I should add the .Expand() extension methods here for the other two entity collections since I want these queries to be as performant as possible and since I am projecting below (only need to display 2 fields for each record in the IQueryable<T>, but thinking I should now after some contemplation.
.Where(x => x.CustomerId == SelectedCustomer.CustomerId)
.Select(x => new Custom
{
CustomerId = x.CustomerId,
...
// I would like to project my Child and GrandChild Collections, i.e. Orders and OrderStatuses here but don't know how to do that. I learned that by projecting, one does not need to "Include/Expand" these extension methods.
});
return ordersWithLatestStatusDate ;
}
---- UPDATE 1 ----
After the great solution from User: lazyberezovsky, I tried the following:
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.Select(o => new Customer
{
Name = c.Name,
LatestOrderDate = o.OrderStatus.Max(s => s.StatusDate)
});
In my hastiness from my initial posting, I didn't paste everything in correctly since it was mostly from memory and didn't have the exact code for reference at the time. My method is a strongly-typed IQueryabled where I need it to return a collection of items of type T due to a constraint within a rigid API that I have to go through that has an IQueryable query as one of its parameters. I am aware I can add other entities/attributes by either using the extension methods .Expand() and/or .Select(). One will notice that my latest UPDATED query above has an added "new Customer" within the .Select() where it was once anonymous. I'm positive that is why the query failed b/c it couldn't be turn into a valid Uri due to LatestOrderDate not being a property of Customer at the Server level. FYI, upon seeing the first answer below, I had added that property to my client-side Customer class with simple { get; set; }. So given this, can I somehow still have a Customer collection with the only bringing back those 2 fields from 2 different entities? The solution below looked so promising and ingenious!
---- END UPDATE 1 ----
FYI, the technologies I'm using are OData (WCF), Silverlight, C#.
Any tips/links will be appreciated.
This will give you list of { OrderId, LatestDate } objects
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
.
UPDATE construct objects in-memory
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.AsEnumerable() // goes in-memory
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
Also grouping could help here.
If I read this correctly you want a Customer entity and then a single value computed from its Orders property. Currently this is not supported in OData. OData doesn't support computed values in the queries. So no expressions in the projections, no aggregates and so on.
Unfortunately even with two queries this is currently not possible since OData doesn't support any way of expressing the MAX functionality.
If you have control over the service, you could write a server side function/service operation to execute this kind of query.

C# - Entity Framework - Join method

I have a table called "PublicUserOfferingSignUp" which contains the following columns.
Id
PublicUserId - foreign key to PublicUser.Id
OfferingId
Created
My application is using Entity Framework, but I am getting stuck with how to join from the PublicUserOfferingSignUp table to the PublicUser table.
I want to obtain a list of PublicUserOfferingSignUp records but ordered by the Name column of the PublicUser table.
Currently I have this ....
return DataBase.PublicUserOfferingSignUps.Join(PublicUser,
But I can't seem to work it out, any ideas ....
Steven
Can anybody help.
Something like that
DataBase.PublicUserOfferingSignUps.Join(Database.PublicUsers,
puosu => puosu.PublicUserId,//PublicUserOfferingSignUps key
pu => pu.Id,//PublicUser key
(puosu, pu) => new {
publicUsersOfferingSignUp = puosu,//we take all data from PubliUserOfferingSignUps
puName = pu.Name//and Name from PublicUser
})
.OrderBy(x => x.puName)//we order by PublicUser Name
.Select(x => x.publicUsersOfferingSignUp );//we take only the PublicOfferingSignUps
Edit : as #M.Schenkel noticed, it would be easier to have a
public virtual PublicUser PublicUser {get;set;}
in your PublicUserOfferingSignUp model
then the query would be
DataBase.PublicUserOfferingSignUps
.OrderBy(puosu => puosu.PublicUser.Name);
easier, no ?
When you use the Entity Framework, the public user should be a property of your PublicUserOfferingSignUp-entity. If not, you can write a LINQ query to join them. For example:
var result = from pu in context.PublicUserOfferingSignUp
join u in context.PublicUser on u.id equals pu.PublicUserId
select pu;
(this code is untested, but should give you the idea).

"Deep loading" with LoadWith

I have a table structure, each table with a primary key field named as "ID" and foreign key names matching their parent table's primary key. Therefore, the tables below have relationships where their primary key appears in another table and the first field in any table is it's primary key:
Category
--------
CategoryID
Title
CategoryList
------------
CategoryListID
CategoryID
ListID
List
----
ListID
Title
DataPoint
---------
DataPointID
RecordedDateTime
DataPointValue
--------------
DataPointValueID
DataPointID
TheValue
The above is a many-to-many join between Category and List, via CategoryList. It is also a one-to-many join from List to DataPoint, DataPoint to DataPointValue.
Using C#/LINQ and given a List of the CategoryID values, I would like to retrieve:
All the List entries attached to the Category I have ID's for. With those List entries, I would like to take the most recent 1 DataPoint, as ordered by RecordedDateTime Descending. From there I would like to retrieve every DataPointValue attached to the DataPoint.
The LINQ I have is:
DBDataContext context = new DBDataContext(ConnectionString);
context.LoadOptions = new DataLoadOptions();
context.LoadOptions.LoadWith<DataPoint>(p => p.DataPoints.OrderByDescending(p.RecordedDataTime).FirstOrDefault());
// this next line is how I get the list of category IDs, but don't worry about that...
List<int> categoryIDs = (from TreeNode n in nodes
select Int32.Parse(n.Value)).Distinct().ToList();
var lists = from i in context.List
join ci in context.CategoryLists on i.ListID equals ci.ListID
join p in context.DataPoints on i.ListID equals p.ListID
join v in context.DataPointValues on p.DataPointID equals v.DataPointID
where categoryIDs.Contains(ci.CategoryID)
orderby i.Title ascending
select new
{
List = i,
DataPoint = p,
DataPointValues = p.DataPointValues
};
But this is obviously not working - the LoadWith is causing me issues. Could someone explain how to construct the LoadWith so that it will cause as few SQL queries as possible to retrieve this (admittedly large) amount of data, please?
Many thanks,
Matt.
You asked this a month ago, but here's an answer anyway...
There are a few issues here:
Once you set the LoadOptions property on the context, you can't change it. You should create and configure your DataLoadOptions object, and when you're done, assign it to the context.
LoadWith specifies what children get automatically loaded with the parent. So loadOptions.LoadWith<DataPoint>(p => p.DataPointValues) would automatically load the DataPoint's children, and not wait until the DataPoint.DataPointValues (or whatever you name it) property is accessed. LoadWith makes the loading non-lazy (eager).
AssociateWith allows you to filter and order the in children that automatically-loading relationship. For example loadOptions.AssociateWith<DataPoint>(p => p.DataPointValues.OrderByDescending(v => v.TheValue)) would sort the DataPointValues by value.
And finally, I'd probably break up your query into two, just to make it easier.
// first setup a DataPoint -> DataPointValue relationship in your DBML
// then set up the DataPointValues to automatically load with DataPoint:
dataLoadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// then assign the load options to the context here
// First query
List<int> listIDs = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryListID))
.Select(cl => cl.ListID)
.ToList();
// Second query(ies) - this isn't the most elegant, but simple is usually better :)
List<DataPoint> dataPoints = new List<DataPoint>();
foreach (int listID in listIDs)
{
DataPoint foundDP = context.DataPoints
.Where(dp => listIDs.Contains(dp.ListID))
.OrderByDescending(dp => dp.RecordedDateTime)
.Take(1)
.SingleOrDefault();
// Remember, at this point DataPointValues will already be loaded into the DataPoint
if (foundDP != null)
dataPoints.Add(foundDP);
}
Anyway, that's a longwinded answer that you may or may not even need! Ah well, it's practice for me, I guess. Hope it helps.
EDIT:
Sorry, started thinking about this...
You could possibly do this instead (cleaner, faster):
loadOptions.LoadWith<List>(l => l.DataPoints);
loadOptions.AssociateWith<List>(l => l.DataPoints.OrderByDescending(dp => dp.RecordedDateTime).Take(1));
loadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// assign the LoadOptions here,
// then:
List<DataPoint> dataPoints = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryID))
.Select(cl => cl.List.DataPoints)
.ToList();

Categories