Custom Linq query for Entity Framework & ASP.NET MVC app - c#

I have been working on an ASP.NET MVC app using Entity Framework. Also, it's my first time developing an ASP.NET MVC app. I have been struggling (close to a month of trying and googling) to write a linq query to display results in jQuery datatable for the below SQL query. It involves various left joins and some columns have null values. It would be great if someone could help me out on this. There are 3 tables as below
Assets
Category
Term
SELECT
Asset.Name AS Name,
Asset.Type AS Type,
Asset.Parent_Asset AS "Parent Asset",
Cat.Category AS Category,
Cat.Parent_Category AS "Parent Category",
T.BUSINESS_TERM AS "Business Term",
T.SHORT_DESCRIPTION AS Description
FROM
(SELECT
CH.DISPLAY AS Name,
CH.TYPE AS Type,
PA.DISPLAY AS Parent_Asset,
CH.CATEGORY_INT_ID
FROM
[Metadata].[dbo].[Asset] CH
LEFT JOIN
[Metadata].[dbo].[Asset] PA ON PA.PARENT_ASSET_ID = CH.ASSET_INT_ID) Asset
LEFT JOIN
(SELECT
CH.DISPLAY AS Category,
PA.DISPLAY AS Parent_Category,
CH.CATEGORY_INT_ID AS Category_Id
FROM
[METADATA].[dbo].[Category] CH
LEFT JOIN
[METADATA].[dbo].[Category] PA ON PA.PARENT_CATEGORY_ID = CH.CATEGORY_INT_ID) Cat ON Asset.CATEGORY_INT_ID = Cat.Category_Id
LEFT JOIN
[Metadata].[dbo].[Term] T ON T.CATEGORY_INT_ID = Cat.Category_Id

Create Stored Procedure in the database where the application is connected
CREATE PROCEDURE sp_GetAsset
AS
BEGIN
SELECT
Asset.Name AS Name,
Asset.Type AS Type,
Asset.Parent_Asset AS ParentAsset,
Cat.Category AS Category,
Cat.Parent_Category AS ParentCategory,
T.BUSINESS_TERM AS BusinessTerm,
T.SHORT_DESCRIPTION AS Description
FROM
(
SELECT
CH.DISPLAY AS Name,
CH.TYPE AS Type,
PA.DISPLAY AS Parent_Asset,
CH.CATEGORY_INT_ID
FROM
[Metadata].[dbo].[Asset] CH
LEFT JOIN
[Metadata].[dbo].[Asset] PA ON PA.PARENT_ASSET_ID = CH.ASSET_INT_ID) Asset
LEFT JOIN
(SELECT
CH.DISPLAY AS Category,
PA.DISPLAY AS Parent_Category,
CH.CATEGORY_INT_ID AS Category_Id
FROM
[METADATA].[dbo].[Category] CH
LEFT JOIN
[METADATA].[dbo].[Category] PA ON PA.PARENT_CATEGORY_ID = CH.CATEGORY_INT_ID) Cat ON Asset.CATEGORY_INT_ID = Cat.Category_Id
LEFT JOIN
[Metadata].[dbo].[Term] T ON T.CATEGORY_INT_ID = Cat.Category_Id
);
END
Create class to get data:
public class TestAsset
{
public string Name { get; set; }
public string Type { get; set; }
public string ParentAsset { get; set; }
public string Category { get; set; }
public string ParentCategory { get; set; }
public string BusinessTerm { get; set; }
public string Description { get; set; }
}
Get data in your Context
public class YourContext : DbContext
{
public List<TestAsset> GetAssets()
{
return this.Query<TestAsset>.FromSql("Exec sp_GetAsset").ToList();
}
}
Use GetAssets Method
using (YourContext context = new YourContext())
{
var list = context.GetAssets();
}

Related

Entity Framework Core - Not In

I'm trying to replicate a SQL statement in EF Core but cant seem to find a way to do it, to set the scene I have the following table structure
Slot -> SlotInstance -> SlotInstanceUser
(a Slot can have many SlotInstances, a SlotInstance can have many SlotInstanceUsers)
When a user registers for a SlotInstance a record is created in SlotInstanceUsers storing the SlotInstanceId and UserId - all good there.
I'm able to write SQL to get a list of slot instances which the user has not registered for e.g.
SELECT
S.StartDate, S.EndDate, S.StartTime, S.EndTime, S.DayOfWeek,
SI.Date
FROM
Slot S WITH (NOLOCK)
INNER JOIN
SlotInstance SI WITH (NOLOCK) ON S.Id = SI.SlotId
WHERE
SI.ID not in (
SELECT
SlotInstanceId
FROM
SlotInstanceUser SIU WITH (NOLOCK)
WHERE
SIU.UserId = #UserID
)
ORDER BY
SI.Date
But I just cant seem to replicate this in EF core - what am I missing?
You can write the LINQ query pretty much the same way as the SQL query. Just remember that in LINQ select is last, variables (aliases) are mandatory, and the equivalent of SQL NOT IN is !Contains. e.g.
var query =
from s in db.Slots
join si in db.SlotInstances on s.Id equals si.SlotId
where !(from siu in db.SlotInstanceUsers
where siu.UserId == userId)
select siu.SlotInstanceId).Contains(si.Id)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
But in EF Core you have more options, especially for joins, since normally the relationships (and associated joins) are encapsulated with navigation properties. So the model you are describing with words in EF Core/C# terms is something like
public class Slot
{
public int Id { get; set; }
// Other properties...
public ICollection<SlotInstance> SlotInstances { get; set; }
}
public class SlotInstance
{
public int Id { get; set; }
// Other properties...
public Slot Slot { get; set; }
public ICollection<SlotInstanceUser> SlotInstanceUsers { get; set; }
}
public class SlotInstanceUser
{
public int Id { get; set; }
// Other properties...
public SlotInstance SlotInstance { get; set; }
}
and the query would be like
var query =
from s in db.Slots
from si in s.SlotInstances
where !si.SlotInstanceUsers.Any(siu => siu.UserId == userId)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
(this actually translates to SQL NOT EXISTS, but that's not essential).
And if you don't need projection, but simply slot instances (with slot info) which the user has not registered for, then it would be simply
var query = db.SlotInstances
.Include(si => si.Slot)
.Where(si => !si.SlotInstanceUsers.Any(siu => siu.UserId == userId))

How to map multiple objects from the same table with Dapper.net?

I have two SQL tables: Teams and Members. Each team contains 3 members, in the database the members' ids are stored.
Sample Database
How could I map the Member objects into the Teams using the Dapper.NET ORM?
public class Team
{
public int? id { get; set; }
public Member MemberA { get; set; }
public Member MemberB { get; set; }
public Member MemberC { get; set; }
}
public class Member
{
public int? id { get; set; }
public string Name { get; set; }
}
public IEnumerable<Team> GetTeams()
{
string sql = "SELECT * FROM Teams t LEFT JOIN Members m ON t.MemberA=m.id AND t.MemberB=m.id AND t.MemberC=m.id";
return m_connection.Query<Team, Member, Member, Member, Team>(sql, (t, m1, m2, m3) =>
{
t.MemberA = m1;
t.MemberB = m2;
t.MemberC = m3;
return t;
}, splitOn: "MemberA,MemberB,MemberC");
}
You need to fix your sql query to have a proper join with the Members Table.
Just change it to
string sql = #"SELECT t.ID, t.MemberA, m1.Id, m1.Name,
t.MemberB, m2.Id, m2.Name,
t.MemberC, m3.Id, m3.Name
FROM Teams t LEFT JOIN Members m1 ON t.MemberA=m1.id
LEFT JOIN Members m2 ON t.MemberB=m2.id
LEFT JOIN Members m3 ON t.MemberC=m3.id";
and your dapper code will work as you expect filling the three Member instance of every single Team retrieved.
Notice that when you use multimapping, you need to place the SplitOn elements in the proper place to have Dapper understand your requirement to create three different Member variables.
Version for MS-Access
string sql = #"SELECT t.ID, t.MemberA, m1.Id, m1.[Name],
t.MemberB, m2.Id, m2.[Name],
t.MemberC, m3.Id, m3.[Name]
FROM (((Teams t LEFT JOIN Members m1 ON t.MemberA=m1.id)
LEFT JOIN Members m2 ON t.MemberB=m2.id)
LEFT JOIN Members m3 ON t.MemberC=m3.id)";

EF 6.1 Database.SqlQuery projection into complex type (many-to-many relationship)

Is it in Entity Framework 6.1 possible to use the Database.SqlQuery command to execute a query, containing a many-to-many relation, and map it back to a DTO (using an intermediate DTO - i know this cannot be done in one go)? And how performed would such an action be?
This example is an extremely simplified version of a problem I'm currently facing. I'm just interested of what can can (cannot) be done with Database.SqlQuery.
I know i can use navigational properties (use Linq) but I'm investigating performance on a far more complex query. This is just a very simplified version of what I'm trying to achieve.
Database
DTO
public class EventDto{
public int EventId {get;set;}
public string Name {get;set;}
public string Slug {get;set;}
public List<ArtistDto> Headliners {get;set;}
}
public class ArtistDto{
public int ArtistId {get;set;}
public string Name {get;set;}
public string Bio {get;set;}
}
Temp DTO
public class EventWithHeadlinersDto{
public int EventId {get;set;}
public string Name {get;set;}
public string Slug {get;set;}
public int ArtistId {get;set;}
public string Name {get;set;}
public string Bio {get;set;}
}
Code
return Context.Database.SqlQuery<EventWithHeadlinersDto>(#"
SELECT * FROM [Events] E
LEFT JOIN [Headliners] H ON E.EventId = H.EventId
LEFT JOIN [Artists] A ON H.ArtistId = A.ArtistId
WHERE E.eventid = #eventId",
new SqlParameter("eventId", eventId))
.ToListAsync();
Requires some coding (basically replicating the EF query materialization process), but doable.
First, the temp DTO should be modified to include all the required fields, taking into account the left joins (using nullable types where needed):
public class EventWithHeadlinersDto
{
// Event Info
public int EventId { get; set; }
public string EventName { get; set; }
public string EventSlug { get; set; }
// Artist Info
public int? ArtistId { get; set; }
public string ArtistName { get; set; }
public string ArtistBio { get; set; }
}
Then you should ensure the SQL SELECT includes all the necessary columns, using the aliases when necessary in order to match DTO property names:
var sql = #"
SELECT
E.EventId, E.Name EventName, E.Slug EventSlug,
A.ArtistId, A.Name ArtistName, A.Bio ArtistBio
FROM [Events] E
LEFT JOIN [Headliners] H ON E.EventId = H.EventId
LEFT JOIN [Artists] A ON H.ArtistId = A.ArtistId
WHERE E.EventId = #eventId";
Then execute the sql query and get the result DTO set:
var dataSet = await query.ToListAsync();
Finally transform it to the desired format:
var eventMap = new Dictionary<int, EventDto>();
var artistMap = new Dictionary<int, ArtistDto>();
foreach (var entry in dataSet)
{
EventDto #event;
if (!eventMap.TryGetValue(entry.EventId, out #event))
{
#event = new EventDto
{
EventId = entry.EventId,
Name = entry.EventName,
Slug = entry.EventSlug,
Headliners = new List<ArtistDto>()
};
eventMap.Add(#event.EventId, #event);
}
if (entry.ArtistId != null)
{
ArtistDto artist;
if (!artistMap.TryGetValue(entry.ArtistId.Value, out artist))
{
artist = new ArtistDto
{
ArtistId = entry.ArtistId.Value,
Name = entry.ArtistName,
Bio = entry.ArtistBio,
};
artistMap.Add(artist.ArtistId, artist);
}
#event.Headliners.Add(artist);
}
}
var resultSet = eventMap.Values.ToList();
Of course in the sample case the result set will contain only 0 or 1 items, but the above is applicable regardless of the applied filtering.

Use Sql View in EF 6.0

I have a CashFlowView:
CREATE VIEW [dbo].[CashFlowView]
AS
WITH CTE AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY RateDate) AS ID
, SUM(CASE WHEN C.CurrencyName = 'Br' THEN T.AmountMoney ELSE 0 END) AS AmountBYR
, SUM(CASE WHEN C.CurrencyName = 'Usd' THEN T.AmountMoney ELSE 0 END) AS AmountUSD
, CR.RateDate AS [DATE]
FROM Transactions AS T
INNER JOIN Accounts AS A ON A.AccountID = T.CurrentAccountID
INNER JOIN Currencies AS C ON C.CurrencyID = A.CurrencyID
RIGHT OUTER JOIN CurrencyRates AS CR ON CR.RateDate = T.ExecutionDate
GROUP BY CR.RateDate
)
SELECT
ID
, A.AmountBYR
, (SELECT SUM(B.AmountBYR) FROM CTE B WHERE B.ID<=A.ID) AS BalanceBYR
, A.AmountUSD
, (SELECT SUM(B.AmountUSD) FROM CTE B WHERE B.ID<=A.ID) AS BalanceUSD
, [Date]
FROM CTE AS A
Then I've added the Entity:
public class CashFlowView
{
[Key]
public int ID { get; set; }
public decimal AmountBYR { get; set; }
public decimal BalanceBYR { get; set; }
public decimal AmountUSD { get; set; }
public decimal BalanceUSD { get; set; }
public DateTime Date { get; set; }
}
And, as I understand, I need to add this code to my context:
public DbSet<CashFlowView> CashFlowView { get; set; }
And now I wanna to use my View:
IList<CashFlowView> listView;
using (var _db = new EconomicAppContext())
{
listView = _db.CashFlowView.ToList();
}
But listView is empty. How I may create correct mapping to View (maybe using migration) and use it?
I did it. Try to combine this article
http://www.paragon-inc.com/resources/blogs-posts/a-certain-point-of-view-part-1-ef-code-first
And use Entity Framework Power Tools to find needed result. And check connection. I've got problems with perfomance, so use dispose method carefully.

NHibernate Linq .Contains code generation has bug?

I'm trying to achieve:
select StoreId, StoreName from Store where StoreId in (
select StoreId from Employee where EmployeeName = 'Steve Jobs')
I have this code:
public class Store
{
public virtual int StoreId { get; private set; }
public virtual string StoreName { get; set; }
public virtual IList<Employee> Staff { get; set; }
}
public class Employee
{
public virtual Store Store { get; set; }
public virtual int EmployeeId { get; private set; }
public virtual string EmployeeName { get; set; }
}
var q = from s in session.Query<Store>()
where
(from e in session.Query<Employee>()
where s.EmployeeName == "Steve Jobs"
select e.Store.StoreId).Contains(s.StoreId)
select s;
NHibernate generates this (aliases stripped):
select s.StoreId, s.StoreName
from Store s
where exists
(
select t.StoreId
from Employee e
left join Store t on t.StoreId = e.StoreId
where e.EmployeeName = 'Steve Jobs'
-- wrongly generated code
and t.EmployeeId = s.StoreId
)
Linq-toSql generates the code correctly:
select s.StoreId, s.StoreName
from Store s
where exists
(
select null
from Employee e
where e.EmployeeName = 'Steve Jobs'
and e.StoreId = s.StoreId
)
Is there a problem with subquery code generation on Linq to NHibernate?
However, HQL works:
var q = session.CreateQuery("from Store as s where s.StoreId in (select e.WorkingInStore.StoreId from Employee as e where e.EmployeeName = 'lennon')").List<Store>();
Certainly looks like a bug, but I think you're overcomplicating the whole query. As I understand, you want all stores where an employee named Steve Jobs is on the payroll. Try:
var q = from s in session.Query<Store>()
where s.Staff.Any(e=>e.EmployeeName == "Steve Jobs")
This should generate the query you want, and it's much cleaner and more readable than the subquery.
I answered a similar question in this post. Instead of using Contains() operator, we can tweak the subquery a little bit and use Any() operator instead.
LINQ to NHibernate WHERE EXISTS IN

Categories