First post on here and quite a simple one.
I've been looking into simplifying some complex queries in an application I'm developing and I'm scratching my head a bit on the below.
So say I have these two classes:
A domain entity "EmailRecipient" (used with EF code-first so expect a SQL table to be generated with the same column names).
public class EmailRecipient
{
public Guid Id { get; set; }
public string FriendlyName { get; set; }
public string ExchangeName { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public string EmailAddress { get; set; }
public string JobTitle { get; set; }
public virtual List<SentEmail> SentEmails { get; set; }
}
and a simple class for JSON serialisation called "EmailLite" defined as
public class EmailLite
{
public string EmailAddress { get; set; }
public Guid Id { get; set; }
public string FriendlyName { get; set; }
}
In my specialised EF6(.1.3) DbContext, I have a DbSet called EmailRecipients.
So naturally executing this linq expression against EmailRecipients
EmailRecipients.Select(x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
});
the generated SQL is
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[EmailAddress] AS [EmailAddress],
[Extent1].[FriendlyName] AS [FriendlyName]
FROM [dbo].[EmailRecipients] AS [Extent1]
So why when I do:
Func<EmailRecipient, EmailLite> projectionFunction = x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
};
EmailRecipients.Select(projectionFunction);
do I get the below (complete) SQL generated:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[FriendlyName] AS [FriendlyName],
[Extent1].[ExchangeName] AS [ExchangeName],
[Extent1].[Surname] AS [Surname],
[Extent1].[Forename] AS [Forename],
[Extent1].[EmailAddress] AS [EmailAddress],
[Extent1].[JobTitle] AS [JobTitle],
[Extent1].[SubscribedOn] AS [SubscribedOn]
FROM [dbo].[EmailRecipients] AS [Extent1]
Any help would be most appreciated!
Cheers,
Sat
IQueryable<T>.Select() takes an Expression<Func<T,TOut>> as parameter, the function you're actually using is IEnumerable<T>.Select() which takes a delegate. Because of this, you are telling EF that from that moment on, you are using IEnumerable rather than IQueryable and the rest of the query will be executed in memory => you're fetching all the columns.
EmailRecipients <-- in memory from here on --> .Select(projectionFunction);
All you need to do is change projectionFunction into an expression and it will work:
Expression<Func<EmailRecipient, EmailLite>> projectionFunction = x => new EmailLite
{
Id = x.Id,
EmailAddress = x.EmailAddress,
FriendlyName = x.FriendlyName
};
Related
I have a get route a that is going to get data of wells and well tests, when I execute the call on swagger, it will take awhile and then give me a call stack error. My problem is I cant figure out how to get a log or idea of where this is happening. The best I have been able to do so far is use point breaks at every step to see how far it gets. I've gotten to the controller route so I know that its grabbing the data just fine, my understanding is that it now has the data, and should use the view model to match and display the data. I have gone through about 100 data samples in the view model and it seems fine but there is 2400 units, all with 5 arrays inside of them. However it will simply error out with no message. Any ideas of whats going on or how to debug this? Is there a way in VS Code so see a better log of something like this or another tool that will do that will help in this situation?
** Service Code: **
public async Task<IEnumerable<SapDispatchViewModel>> GetDispatchDeliveryForSap()
{
var result = await _dispatchRepo.GetDispatchDeliveryForSap(TenantId);
var view = new List<SapDispatchViewModel>();
foreach (SapDispatch row in result)
{
var sapView = _mapper.Map<SapDispatch, SapDispatchViewModel>(row);
var items = await _dispatchItemRepo.GetDispatchItemsByTruckForSap(row.DispatchTruckId);
var viewItems = _mapper.Map<IEnumerable<SapDispatchItem>, IEnumerable<SapDispatchItemViewModel>>(items);
sapView.Items = viewItems;
view.Add(sapView);
}
return view;
}
** It calls this GetDispatchDeliveryForSap first: **
public async Task<IEnumerable<SapDispatch>> GetDispatchDeliveryForSap(string TenantId)
{
string deliveryType = "Delivery";
//resort to raw SQL to assist with performance improvements
FormattableString sql = $#"
WITH cte_latestStatus AS
( SELECT * FROM (
SELECT
s.TenantId,
s.DispatchId,
s.DispatchHeaderId,
s.RequestedArrival,
s.EstimatedArrival,
s.Status,
u.FirstName + ' ' + u.LastName UserName,
s.CreateDate StatusChangeDate,
row_number() over(partition by DispatchHeaderId order by CreateDate desc) as rn
FROM
DispatchStatus s
JOIN AspNetUsers u on s.CreateUserId = u.Id
) t
WHERE t.rn = 1
)
select w.wellid,
w.wellname,
wo.ErpId,
wc.ContractorName + ' ' + w.RigNumber Rig,
w.CountyParish County,
w.State,
d.type DispatchType,
u.LastName + ',' + u.FirstName OrderedBy,
ds.RequestedArrival RequestedDate,
dt.DriverName,
dt.SwamperName,
dt.TicketNumber,
dt.DispatchTruckId
from well w
join Dispatch d on w.wellid = d.DestinationWellId
join cte_latestStatus ds on d.DispatchId = ds.DispatchId and d.HeaderId = ds.DispatchHeaderId
join DispatchTruck dt on d.DispatchId = dt.DispatchId
join AspNetUsers u on d.CreateUserId = u.Id
left join WellContractorRef wcr on w.WellId = wcr.WellId
left join Contractor wc on wcr.ContractorId = wc.ContractorId
left join WellOperatorRef wor on w.WellId = wor.WellId
left join Operator wo on wor.OperatorId = wo.OperatorId
--join DispatchItem di on dt.DispatchTruckId = di.DispatchTruckId
where d.TenantId = {TenantId}
and d.type = {deliveryType}
and (ds.Status = 'Completed' or dt.status = 'Completed')
order by w.wellname"
;
var result = await context.SapDispatches.FromSqlInterpolated(sql).AsNoTracking().ToListAsync();
return result;
}
}
}
** Then maps via the view model to create the list: **
namespace Mudman.Model.ViewModels
{
public class SapDispatchViewModel
{
public string WellId { get; set; }
public string WellName { get; set; }
public string ErpId { get; set; }
public string Rig { get; set; }
public string County { get; set; }
public string State { get; set; }
public string DispatchType { get; set; }
public string OrderedBy { get; set; }
public DateTime? RequestedDate { get; set; }
public string DriverName { get; set; }
public string SwamperName { get; set; }
public long? TicketNumber { get; set; }
public IEnumerable<SapDispatchItemViewModel> Items { get; set; }
}
public class SapDispatchItemViewModel
{
public string ErpId { get; set; }
public Decimal? Price { get; set; }
public Decimal? Quantity { get; set; }
public string Size { get; set; }
public string Unit { get; set; }
}
}
** From there, it runs the foreach on the GetDispatchItemsForTruckSap: **
public async Task<IEnumerable<SapDispatchItem>> GetDispatchItemsByTruckForSap(string dispatchTruckId)
{
//resort to raw SQL to assist with performance improvements
FormattableString sql = $#"
WITH cte as (
SELECT
COALESCE(ProductId, ExpenseId) AS SalesItemID,
Price,
Quantity
FROM DispatchItem
WHERE DispatchTruckId = {dispatchTruckId}
)
SELECT si.ErpId,
cte.Price,
cte.Quantity,
si.Size,
si.Unit
FROM SalesItem si
INNER JOIN cte on cte.SalesItemID = si.SalesItemId"
;
var result = await context.SapDispatchItems.FromSqlInterpolated(sql).AsNoTracking().ToListAsync();
return result;
}
}
}
** Maps with the Item View Model: **
public class SapDispatchItemViewModel
{
public string ErpId { get; set; }
public Decimal? Price { get; set; }
public Decimal? Quantity { get; set; }
public string Size { get; set; }
public string Unit { get; set; }
}
}
** Then it will hit the return and thats where it will error out.
Also, here is what the callstack is looking like when you hit that return.
Try turning on Break When Thrown on Common Language Runtime Exceptions and it should break at the error:
Consider an Sqlite database, whose partial schema is shown below (we are not considering the Book_Tag table here). Note the many-to-many relationship between media items and tags using the link table Media_Tag:
An object model for these tables is as follows:
public enum MediaType
{
Dvd,
BluRay,
Cd,
Vhs,
Vinyl,
Other
}
public class MediaItem
{
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public string name { get; set; }
}
currently, Dapper is being used to read from the Media table, but without considering tags. The code is as follows:
public IEnumerable<MediaItem> readAll()
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media;";
return db.Query<MediaItem>(sql);
}
}
public MediaItem readById(int id)
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media WHERE id = #id;";
var #params = new { id = id };
return db.Query<MediaItem>(sql, #params).First();
}
}
How to change this so that the tag property of MediaItem is considered when creating the objects, for both cases (read by id and read all rows from the table)? Is a join query required? I'm sure Dapper has a way of doing this nicely, but I don't know how it's done.
You are not interested in anything from the link table so something like this SQL should do:
SELECT M.Id, M.title, M.type, M.Number, M.image, M.runningTime, M.releaseYear, T.Id, T.Name FROM Media as M
INNER JOIN Media_Tag AS MT ON M.id = MT.mediaId
INNER JOIN Tags AS T ON T.id = MT.tagId
If SqLite allows you can use M.*, T.* instead.
I have taken the liberty to add Id properties to your entity classes. I think you are going to need it, otherwise all your tags will be different instead of being unique. You might make it work without it, but it should make your life easier.
public class MediaItem
{
public int Id { get; set; } // New
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public int Id { get; set; } // New
public string name { get; set; }
}
Since both your entity classes have a unique id, you will have to pick them up and make sure they are unique going through the results. We do that by using dictionaries to keep them. I'm only showing the ReadAll, you should be able to do ReadById accordingly.
string sql = "<see above>";
using (var db = new SqliteConnection(this.connectionString))
{
var mediaDictionary = new Dictionary<int, Media>();
var tagDictionary = new Dictionary<int, Tag>();
var list = db.Query<Media, Tag, Media>(
sql,
(media, tag) =>
{
Media mediaEntry;
if (!mediaDictionary.TryGetValue(media.Id, out mediaEntry))
{
// Haven't seen that one before, let's add it to the dictionary
mediaEntry = media;
mediaDictionary.Add(mediaEntry.Id, mediaEntry);
}
Tag tagEntry;
if (!tagDictionary.TryGetValue(tag.Id, out tagEntry))
{
// Haven't seen that one before, let's add it to the dictionary
tagEntry = tag;
tagDictionary.Add(tagEntry.Id, tagEntry);
}
// Add the tag to the collection
mediaEntry.Tags.Add(tagEntry);
return mediaEntry;
},
splitOn: "Id") // This default and could be omitted
.Distinct()
.ToList();
I have the following Dapper query:
var orderModels = db.Query<OrderModel>(#"
SELECT
o.[Id],
o.[CustomerId],
o.[DeliveryAddress_FirstName],
o.[DeliveryAddress_LastName],
o.[DeliveryAddress_Line1],
o.[DeliveryAddress_Line2],
o.[DeliveryAddress_City],
o.[DeliveryAddress_State],
o.[DeliveryAddress_PostCode],
o.[DeliveryAddress_Country],
o.[BillingAddress_FirstName],
o.[BillingAddress_LastName],
o.[BillingAddress_Line1],
o.[BillingAddress_Line2],
o.[BillingAddress_City],
o.[BillingAddress_State],
o.[BillingAddress_PostCode],
o.[BillingAddress_Country]
FROM
[Order] o
");
And I'd like to load it into a data model of the following structure:
public class OrderModel
{
public int Id { get; set; }
public int CustomerId { get; set; }
public AddressModel DeliveryAddress { get; set; }
public AddressModel BillingAddress { get; set; }
}
public class AddressModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
}
I have tried to look this up, and there is a feature in Dapper called multi-mapping. However, I can't figure out how to use it in my use-case when I'm not joining results from multiple tables.
It feels like a very common scenario for ORM like Dapper, so I'm sure I'm just missing something obvious. If you're more knowledgeable about Dapper, please help.
What is the best way to accomplish this?
Dapper does not care about joins, you just need to provide it correct fields for splitOn options like:
var orderModels = db.Query<OrderModel, AddressModel, AddressModel, OrderModel>(#"
SELECT
o.[Id],
o.[CustomerId],
o.[DeliveryAddress_FirstName] AS [FirstName], // Field names should match properties of your model
o.[DeliveryAddress_LastName] AS [LastName],
o.[DeliveryAddress_Line1] AS [Line1],
o.[DeliveryAddress_Line2] AS [Line2],
o.[DeliveryAddress_City] AS [City],
o.[DeliveryAddress_State] AS [State],
o.[DeliveryAddress_PostCode] AS [PostCode],
o.[DeliveryAddress_Country] AS [Country],
o.[BillingAddress_FirstName] AS [FirstName],
o.[BillingAddress_LastName] AS [LastName],
o.[BillingAddress_Line1] AS [Line1],
o.[BillingAddress_Line2] AS [Line2],
o.[BillingAddress_City] AS [City],
o.[BillingAddress_State] AS [State],
o.[BillingAddress_PostCode] AS [PostCode],
o.[BillingAddress_Country] AS [Country]
FROM
[Order] o
",
(order, deliveryAddress, billingAddress) => {
order.DeliveryAddress = deliveryAddress;
order.BillingAddress = billingAddress;
return order;
},
splitOn: "FirstName,FirstName");
It is explained in this article.
Also, the select's field names have to match model property names for Dapper to figure out the mapping automatically.
Using C# MVC5 Visual studio 2015.
I have a method that contains the following code:
public List<OffersOnPropertyViewModel> Build(string buyerId)
{
var filtered = _context.Properties.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
var model = filtered.Select(c =>
{
var item = new OffersOnPropertyViewModel()
{
PropertyType = c.PropertyType,
NumberOfBedrooms = c.NumberOfBedrooms,
StreetName = c.StreetName,
Offers = c.Offers.Where(d => d.BuyerUserId == buyerId).Select(x => new OfferViewModel
{
Id = x.Id,
Amount = x.Amount,
CreatedAt = x.CreatedAt,
IsPending = x.Status == OfferStatus.Pending,
Status = x.Status.ToString(),
BuyerUserId = x.BuyerUserId
}),
};
return item;
}).ToList();
//TODO: refactor, shorten linq, duping where clause
return model;
}
Here is the model:
public class Property
{
[Key]
public int Id { get; set; }
[Required]
public string PropertyType { get; set; }
[Required]
public string StreetName { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int NumberOfBedrooms { get; set; }
[Required]
public string SellerUserId { get; set; }
public bool IsListedForSale { get; set; }
public ICollection<Offer> Offers { get; set; }
}
In the DB Offers table has the property id as its FK.
The method fails at runtime saying the Value cannot be null.
When I step through I notice the filtered results (in the example its 1 result), is saying offers is null. Although the query just filtered the results based on "x.Offers".
I simply need a way to retrieve a list of property's that have offers made by the buyerId provided. Is my approach wrong? or am i missing a one liner?
Thanks
You will need to add Include() to your LINQ query to bring in child objects, as follows:
var filtered = _context.Properties.Include("Offers")
.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
The reason your filter works with the Any() is because when generating the SQL query, this part forms the WHERE clause and is not included in the SELECT.
public class User
{
public int ID { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
}
class TestDbContext : DbContext
{
public TestDbContext()
: base("DefaultConnectionString")
{
}
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
}
Above are the model definitions and DbContext difinitions. I want to add a new address for the user, so i wrote my code as bellow:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
user.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
My doubt is why there are 3 SQL queries are executed in this code?
It's generated in FirstOrDefault
SELECT TOP (1)
[Extent1].[ID] AS [ID],
[Extent1].[EmailAddress] AS [EmailAddress]
FROM [dbo].[Users] AS [Extent1]
WHERE 1 = [Extent1].[ID]
It's generated in user.Addresses.Add
exec sp_executesql N'SELECT
[Extent1].[ID] AS [ID],
[Extent1].[City] AS [City],
[Extent1].[Street] AS [Street],
[Extent1].[Postcode] AS [Postcode],
[Extent1].[User_ID] AS [User_ID]
FROM [dbo].[Addresses] AS [Extent1]
WHERE ([Extent1].[User_ID] IS NOT NULL)
AND ([Extent1].[User_ID] = #EntityKeyValue1)',N'#EntityKeyValue1 int',#EntityKeyValue1=1
It's generated in SaveChanges
exec sp_executesql N'INSERT [dbo].[Addresses]([City], [Street], [Postcode], [User_ID])
VALUES (#0, #1, #2, #3)
SELECT [ID]
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [ID] = scope_identity()',N'#0 nvarchar(max) ,#1 nvarchar(max) ,#2 nvarchar(max) ,#3 int',#0=N'City',#1=N'Street',#2=N'Postcode',#3=1
How can I avoid the second SQL?
The Addresses nav property is lazy loading when you access the property (i.e. user.Addresses), which is why you're getting the second SQL command.
Try disabling lazy loading and see if that works (don't forget to initialize the Addresses property in a constructor for User e.g.:
public User()
{
Addresses = new HashSet<Address>();
}
You can even prevent the first two queries!
You already know the user's ID value, so all you have to do is set the foreign key value in Address. Of course, Address should have this property:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public int UserID { get; set; } // Set this property
public User User { get; set; }
}
The pair User and UserID is called a foreign key association, which is the preferred way to deal with associations in EF (precisely because it can reduce the number of queries).
Have you tried changing the class definition slightly:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public virtual User User { get; set;}
}
so that now you can write:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
context.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
User = user
});
context.SaveChanges();
As already pointed out the problem here is your Addresses property is a navigation property so when you access it EF is generating a SELECT statement to load the collection in. To avoid this from happening you have 2 options:
Eager load the addresses when you load the User so you take the hit when you first load the user e.g. Users.Include(x => x.Addresses)
Disable lazy loading on that particular property by making the Addresses property non-virtual
I would add a UserId foreign key to the Address class, then I'd do this:
var context = new TestDbContext();
context.Addresses.Add(new Address()
{
UserId = 1,
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
No need to retrieve the user or the user's existing addresses
Foreign keys make Entity Framework is easier to use:
Why does Entity Framework Reinsert Existing Objects into My Database?
Making Do with Absent Foreign Keys
And relationship fix-up will synchronise the navigation property:
http://msdn.microsoft.com/en-gb/data/jj713564.aspx