How to Return class with list using LINQ in C#? - linq

I have one "Profiles" and "Addresses" table Profiles table contain one record of person and Addresses table contains its multiple address.
I want to write LINQ query to return single person record with its multiple address in one Query. So I have created one custom class with "profileId" ,"firstName" and list of address.
I need a output like this please help.
1 (profileId)
Mark (firstName)
------Sanday (address one)
------Stronsay (address two)
------Foula (address three)
public class profileDetails
{
public int profileIdData { get; set; }
public string firstNameData { get; set; }
public List<lstAddress> lstAddressData { get; set; }
}
public class lstAddress
{
public int addressId { get; set; }
public string address { get; set; }
}
//This is my query
var result = (from profile in DbContext.Profiles
join address in DbContext.Addresses
on profile.profileId equals address.addressId
select new profileDetails()
{
profileIdData = profile.profileId,
firstNameData = profile.firstName,
lstAddressData = new { address.addressId, address.address }.toList()
}
)

Have you tried using the Include method? Something like this:
var result = (from profile in DbContext.Profiles
join address in DbContext.Addresses
on profile.profileId equals address.addressId).Include("lstAddressData");
(the lstAddressData name may be incorrect in my example above, it refers to the reference property you want to populate with address items)

Related

Correct interpretation of SQL request by EF Core

I have a certain table in the database that stores the following objects:
public partial class Invoice
{
public string DocumentNumber { get; set; }
public DateTime? DocumentDate { get; set; }
public string DocumentReference { get; set; }
public string SerialNumber { get; set; }
public string ProductCode { get; set; }
public string Description { get; set; }
public string Certificate { get; set; }
public string Language { get; set; }
public string Email { get; set; }
}
I also have a query that returns me the number of specific elements:
SELECT Count(*)
FROM (
SELECT DocumentNumber,DocumentDate,DocumentReference
FROM vInvoiceSwivelInfoWeb
WHERE Email = 'someemail#gmail.com' AND Language = 'FR'
GROUP BY DocumentNumber,DocumentDate,DocumentReference
) AS T
The answer looks something like this:
How to use EF to make such a request and get a numerical answer?
I tried like this:
_context.Database.ExecuteSqlRawAsync($"..some SQL query..")
but I do not get the expected result.
UPD: Having received the answer about the impossibility of fulfilling this request through EF, the following question reasonably arose: Is it possible to make this request using LINQ?
You can Leverage ADO.NET via the Context.Database property.
Unfortunately, there is no way to get the count from the database using EF Core execute methods if you have a custom query that is not related to your entities.
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT Count(*) From Table1";
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
// do something with result
}
}
for Updated question
var count = from a in _context.vInvoiceSwivelInfoWeb
where a.Email == "someemail#gmail.com" && a.Language == "FR"
group new { a.DocumentNumber , a.DocumentReference , a.DocumentDate } by a into g
select g.Count()
also, it's important to know which version of EF-Core are you using:
currently, if you are using EF-Core 3 group-by doesn't translate to SQL command so you have to do it on client-side:
check this link :
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
for EF-Core 3.0 - 3.1.1
var count = _context.vInvoiceSwivelInfoWeb
.Where(a => a.Email == "someemail#gmail.com" && a.Language == "FR" ).ToList()
.GroupBy(a => new { a.DocumentNumber ,a.DocumentDate, a.DocumentReference }).Count();

Join LINQ queries from multiple contexts

I am trying to create a ViewModel for data that I want to display in my view. The issue is, the data being displayed is spread across 2 databases and multiple tables within each. I've read that you cannot join Linq queries across multiple contexts, which makes sense, and I've also read that we can't use Code-First in EF5 to use Stored Procedures....which led me to using 3 different Linq queries and attempt to combine them into 1 ViewModel...I'm just not sure how to get there.
Here's my Linq queries:
var csdContext = new CSDContext(CustomerCode);
var masterContext = new MasterContext();
//Only returns 1 row - which is what we want.
List<Site> sites = (from s in csdContext.Sites
join sa in csdContext.SiteAddresses
on s.SiteID equals sa.SiteID
join a in csdContext.Addresses
on sa.AddressID equals a.AddressID
join spv in csdContext.SiteProductVersions
on s.SiteID equals spv.ProductVersionID
where s.SiteID == id
select s).ToList();
//List
List<States> states = (from s in masterContext.StatesTable
select s).ToList();
My ViewModel looks like this:
public class SiteDetailsViewModel
{
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string ZipCode { get; set; }
public string OfficePhone { get; set; }
public string MobilePhone { get; set; }
public string AlternativePhone { get; set; }
public int ProductVersionID { get; set; }
}
Basically, I need the following data from these tables:
csdContext - Address
Address
Address2
City
ZipCode
csdContext - Sites
OfficePhone
MobilePhone
AlternativePhone
csdContext - SiteProductVersions
ProductVersionID
masterContext - States
StateCode
Here's how the tables are joined in SQL:
SELECT csd_a.Address, csd_a.Address2, csd_a.City, mstr_st.StateCode, csd_a.ZipCode, csd_s.OfficePhone, csd_s.MobilePhone,
csd_s.AlternativePhone, csd_spv.ProductVersionID
FROM CSD.dbo.Sites AS csd_s
INNER JOIN CSD.dbo.SiteAddress AS csd_sa ON csd_sa.SiteID = csd_s.SiteID
INNER JOIN CSD.dbo.Address AS csd_a ON csd_a.AddressID = csd_sa.AddressID
INNER JOIN CSD.dbo.SiteProductVersions AS csd_spv ON csd_s.SiteID = csd_spv.SiteID
INNER JOIN MasterDB.dbo.States AS mstr_st ON mstr_st.StateID = csd_a.StateID
I can't figure out how to merge these 3 results to create the ViewModel data for SiteDetailsViewModel. Can anyone help?
If you materialize your queries as described in OP you can use this:
var query = sites.Join(
states,
si => si.StateID,
st => st.StateID,
(si, st) => new SiteDetailsViewModel
{
Address = si.Address,
Address2 = si.Address2,
City = si.City,
StateCode = st.StateCode,
ZipCode = si.ZipCode,
OfficePhone = si.OfficePhone,
MobilePhone = si.MobilePhone,
AlternativePhone = si.AlternativePhone,
ProductVersionID = siProductVersionID
});
One possible alternate solution is to create views in DB #2 of the tables in DB #1. Then you can model/map those views in EF for DB #2. You would of course still need a context for DB #1 if you need to update any of the tables. But the benefit of this solution is that you can do all of your read access on a single context and get joins at the server instead of in memory.
So why don't you write it like this:
from ...
...
where s.SiteID == id
select new SiteDetailsViewModel()
{
Address = sa.Address,
Address2 = as.Address2,
..
}

Using LINQ to combine similar rows of grouped data

I've got some data in a table that looks like so:
Recipe | Category | Email
What I'd like to do is pull this data back from the source and put it into something that looks like so:
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public List<string> Names {get; set; }
}
Grouping by the Recipe and Category ids and putting all the emails that into the list.
So, what I've tried is to do something like this:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
// ? Not sure how to stick the list of names in here
};
list is the data pulled back via entity framework.
But this isn't quite right - I think I'm close here (maybe). What am I missing here on this?
Follow-up:
Thanks to Aducci for clearing this up. The answer is that you can do this:
Names = aRecipe.Select(x => x.Name)
and this will add all those Names which are in each group into the Names collection for that group. Pretty nifty.
I would modify your class to look like this
public class RecipeItem
{
public long Recipe { get; set; }
public long Category { get; set; }
public IEnumerable<string> Names {get; set; }
}
And your link to entities query to:
var recipeItems =
from entry in list
group entry by new { entry.Recipe, entry.Category}
into aRecipe
select new RecipeItem()
{
Recipe = aRecipe.Key.Recipe,
Category = aRecipe.Key.Category,
Names = aRecipe.Select(x => x.Name)
};

LINQ query returning a List<> as a class member

Given the follow data class,
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
}
how can I get a sub-query into the ItemNames field?
My current query of
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
};
doesn't seem to be doing the trick...
The data structure is
MenuHeaders MenuItems
----------- ---------
ID ID
HeaderName <-(FK)--MenuHeaderID
MenuItemName
I ended up just changing from a List to IEnumerable. This fixed it.
Wouldnt you want to just put a where in your sub-select to filter that down to all the menu items with the MenuHeaderID equals mh.HeaderName. You can just .Equals() with the StringComparison type if you want as well.
Here is an example...
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName where mhi.MenuHeaderID = mh.HeaderName).ToList<string>()
};
My guess is that your not initiliazing the list within your class. I basing this off the experience I was having with Nhibernate.
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
public EmployeeMenu()
{
ItemNames=new List<string>();
}
}
Hope this helps.
Okay. Try replacing
(from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
by
mh.MenuItems
.AsEnumerable()
.Select(mhi => mhi.MenuItemName)
.ToList()
I question if you want a where clause in there somewhere, but this should get you past the runtime exception.
Any time you see an error message of the form "LINQ to Entities does recognize the method ... and this method can not be translated into a store expression" LINQ to Entities is telling you that it can't figure out how to translate part of the expression tree into a SQL statement. This means you need to pull things client side so that LINQ to Entities doesn't try to translate something that it can't translate.

LinqToSQl and the Member access not legal on type exception

The basic problem...
I have a method which executes the following code:
IList<Gig> gigs = GetGigs().WithArtist(artistId).ToList();
The GetGigs() method gets Gigs from my database via LinqToSql...
So, when GetGigs().WithArtist(artistId).ToList() is executed I get the following exception:
Member access 'ListenTo.Shared.DO.Artist Artist' of 'ListenTo.Shared.DO.Act' not legal on type 'System.Collections.Generic.List`1[ListenTo.Shared.DO.Act]
Note that the extension function "WithArtist" looks like this:
public static IQueryable<Gig> WithArtist(this IQueryable<Gig> qry, Guid artistId)
{
return from gig in qry
where gig.Acts.Any(act => (null != act.Artist) && (act.Artist.ID == artistId))
orderby gig.StartDate
select gig;
}
If I replace the GetGigs() method with a method that constructs a collection of gigs in code (rather than from the DB via LinqToSQL) I do NOT get the exception.
So I'm fairly sure the problem is with my LinqToSQl code rather than the object structure.
However, I have NO IDEA why the LinqToSQl version isnt working, so I've included all the associated code below. Any help would be VERY gratefully receivced!!
The LinqToSQL code....
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
return from g in DBContext.Gigs
let acts = GetActs(g.ID)
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs()
{
return from a in DBContext.Acts
join artist in DBContext.Artists on a.ArtistID equals artist.ID into art
from artist in art.DefaultIfEmpty()
select new ListenTo.Shared.DO.Act
{
ID = a.ID,
Name = a.Name,
Artist = artist == null ? null : new Shared.DO.Artist
{
ID = artist.ID,
Name = artist.Name
},
GigId = a.GigID
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs(Guid gigId)
{
return GetActs().WithGigID(gigId);
}
I have included the code for the Act, Artist and Gig objects below:
public class Gig : BaseDO
{
#region Accessors
public Venue Venue
{
get;
set;
}
public System.Nullable<DateTime> EndDate
{
get;
set;
}
public DateTime StartDate
{
get;
set;
}
public string Name
{
get;
set;
}
public string Description
{
get;
set;
}
public string TicketPrice
{
get;
set;
}
/// <summary>
/// The Act object does not exist outside the context of the Gig, therefore,
/// the full act object is loaded here.
/// </summary>
public IList<Act> Acts
{
get;
set;
}
#endregion
}
public class Act : BaseDO
{
public Guid GigId { get; set; }
public string Name { get; set; }
public Artist Artist { get; set; }
}
public class Artist : BaseDO
{
public string Name { get; set; }
public string Profile { get; set; }
public DateTime Formed { get; set; }
public Style Style { get; set; }
public Town Town { get; set; }
public string OfficalWebsiteURL { get; set; }
public string ProfileAddress { get; set; }
public string Email { get; set; }
public ImageMetaData ProfileImage { get; set; }
}
public class BaseDO: IDO
{
#region Properties
private Guid _id;
#endregion
#region IDO Members
public Guid ID
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
}
}
I think the problem is the 'let' statement in GetGigs. Using 'let' means that you define a part of the final query separately from the main set to fetch. the problem is that 'let', if it's not a scalar, results in a nested query. Nested queries are not really Linq to sql's strongest point as they're executed deferred as well. In your query, you place the results of the nested query into the projection of the main set to return which is then further appended with linq operators.
When THAT happens, the nested query is buried deeper into the query which will be executed, and this leads to a situation where the nested query isn't in the outer projection of the query to execute and thus has to be merged into the SQL query ran onto the DB. This is not doable, as it's a nested query in a projection nested inside the main sql query and SQL doesn't have a concept like 'nested query in a projection', as you can't fetch a set of elements inside a projection in SQL, only scalars.
I had the same issue and what seemed to do the trick for me was separating out an inline static method call that returned IQueryable<> so that I stored this deferred query into a variable and referenced that.
I think this is a bug in Linq to SQL but at least there is a reasonable workaround. I haven't tested this out yet but my assumption is that this problem may arise only when referencing static methods of a different class within a query expression regardless of whether the return type of that function is IQueryable<>. So maybe it's the class that holds the method that is at the root of the problem. Like I said, I haven't been able to confirm this but it may be worth investigating.
UPDATE: Just in case the solution isn't clear I wanted to point it out in context of the example from the original post.
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
var acts = GetActs(g.ID); // Don't worry this call is deferred
return from g in DBContext.Gigs
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
Note that while this should correct the issue at hand there also seems to be another issue in that the deferred acts query is being accessed in each element of the projection which I would guess would cause separate queries to be issued to the database per row in the outer projection.
I don't see anything in your classes to indicate how LINQ to SQL is meant to work out which column is which, etc.
Were you expecting the WithArtist method to be executed in .NET, or converted into SQL? If you expect it to be converted into SQL, you'll need to decorate your Gig class with appropriate LINQ to SQL attributes (or configure your data context some other way). If you want it to be executed in code, just change the first parameter type from IQueryable<Gig> to IEnumerable<Gig>.
I found out that an issue like this (which I also had recently) can be resolved, if you convert the IQueryable (or Table) variable Gigs into a list like so
return from g in DBContext.Gigs.ToList()
...
If that still doesn't work, do the same for all the IQueryables. The reason behind seems to me that some queries are too complex to be translated into SQL. But if you "materialize" it into a list, you can do every kind of query.
Be careful, you should add "filters" (where conditions) early because too much memory consumption can become a problem.

Resources