I have written a code like this in my .NET project:
var v = ctx.Items
.Where(x => x.userid== user.userid)
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrenctPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions.Where(p => p.TransactionDate >= intoPast && p.TransactionDate <= endDate).Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0 && ((filterWord == "") || (filterWord != "" && x.Title.ToLower().Contains(filterWord.ToLower()))));
where "ctx" is my object context variable...
And this is the ViewModelClass that I use:
public class MyViewModel
{
public string Title { get; set; }
public int Sales { get; set; }
public string ItemID { get; set; }
public double CurrentPrice { get; set; }
}
The thing that most bugs me here is the sales property... As you can see i set its value in select statement. This way all my data gets enumerated every time...
What I was thinking here is to create a method called "getsales()"... And then to just simply call the GetSales method in my where statement like this:
.Where(x=>X.GetSales(/*neccesary parameters here*/)...)
In order to avoid having multiple enumerations...
But I'm not really sure how to do it...
Can someone help me out here?
I think this is what you're looking for:
var v = ctx.Items
.Where(x =>
x.userid == user.userid &&
(filterWord == "" || x.Title.ToLower().Contains(filterWord.ToLower())))
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrentPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions
.Where(p =>
p.TransactionDate >= intoPast &&
p.TransactionDate <= endDate)
.Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0);
Related
So I've found a way to do this, but I think its rather ugly and a bit hacky.
I have a Contact model which contains a list of messages, my models can be seen below:
public class Contact
{
[Key]
public int ContactId { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public ICollection<Message> Messages { get; set; } = new Collection<Message>();
}
public class Message
{
[Key]
public int MessageId { get; set; }
public string MessageContent { get; set; }
public string Sender { get; set; }
public string Receiver { get; set; }
public DateTime DateCreated { get; set; }
}
what I wanted to do, and which I have done (badly) is combine the list of messages from both contacts models like by creating a viewModel containing a list of messages, and looping through the results of the contact messages and adding into the view model as seen below:
public class ContactMessageViewModel
{
public ICollection<Message> Messages { get; set; } = new Collection<Message>();
}
public async Task<ContactMessageViewModel> GetContactMessages(string username, string contactName)
{
ContactMessageViewModel MessagesList = new ContactMessageViewModel();
var messagesListUser = await _dbContext.Contact
.Include(m => m.Messages)
.Where(x =>
(x.Owner == username) && x.Name == contactName).ToListAsync();
var messagesListContact = await _dbContext.Contact
.Include(m => m.Messages)
.Where(x =>
(x.Owner == contactName) && x.Name == username).ToListAsync();
foreach(var message in messagesListContact)
{
foreach(var m in message.Messages)
{
MessagesList.Messages.Add(m);
}
}
foreach (var message in messagesListUser)
{
foreach (var m in message.Messages)
{
MessagesList.Messages.Add(m);
}
}
return MessagesList;
}
This just feels super duper ugly to me, and I feel there is a better way to do this. I'm wondering if there is a better looking (and better performing) way to do this? Possibly just using linq?
Thanks!
You can combine the queries into a single query, and then return all the messages:
return new ContactMessageViewModel
{
Messages = await _dbContext.Contact
.Include(m => m.Messages)
.Where(x =>
(x.Owner == username && x.Name == contactName) ||
(x.Owner == contactName && x.Name == username))
.SelectMany(x => x.Messages)
.ToListAsync()
};
Are you able to use something similar to the below which checks for either.
var messagesList = await (from c in_dbContext.Contact
from m in c.Messages
where (c.Owner == username && c.Name == contactName) || (c.Owner == contactName && c.Name == username)
select m).ToListAsync();
The code above assume owner and name will never be the same, otherwise you could store the username and contact name in an array and check the owner and name are both in the array.
The alternative is to store the IQueryable<Messages> in a variable, without doing a ToList. This won't hit the database until you need to enumerate. The benefit of this is you can manipulate the IQueryable and not hit the database until you need the items.
It seems you can do it in one query:
var messagesList = await _dbContext.Contact
.Include(m => m.Messages)
.Where(x => (x.Owner == username && x.Name == contactName)
|| (x.Owner == contactName && x.Name == username))
.ToListAsync();
and use it to build MessagesList.Messages.
Or, if it is ok to get in results entries with x.Owner == x.Name (or such situations are impossible) you can do:
var names = new []{contactName, username};
var messagesList = await _dbContext.Contact
.Include(m => m.Messages)
.Where(x => names.Contains(x.Owner) && names.Contains(x.Name))
.ToListAsync();
UPD
It seems that you need only messages, so you can try something like this:
var messagesList = await _dbContext.Contact
.Where(x => (x.Owner == username && x.Name == contactName)
|| (x.Owner == contactName && x.Name == username))
.SelectMany(m => m.Messages)
.ToListAsync();
// foreach m in messagesList MessagesList.Messages.Add(m);
I have a problem with type convert var to List<SubToSubMenu>.
Firstly, I select data from database that is ok !!!!. I received data with var variable, but i can't convert var type to List<SubToSubMenu> data type.
This is my LINQ statement:
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList();
This is SubToSubMenu class:
[Table("SubToSubMenus")]
public class SubToSubMenu : AceEntityBase
{
public SubToSubMenu()
{ }
[Key]
public string ID { get; set; }
public string SubToSubMenuName { get; set; }
public string Description { get; set; }
public string SubMenuID { get; set; }
}
var is not a type, it's syntactic sugar. You have an anonymous type that has no relation whatsoever to your SubToSubMenu type.
Instead of projecting into an anonymous type:
.Select(s => new { ... })
Project into the type you want:
.Select(s => new SubToSubMenu { ... })
You shouldn't need to project here. You're already selecting the SubToSubMenu from the database, so there should be no need to 'recreate' the class later in the expression chain.
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
This is good so far. You've joined two tables and applied the correct filters.
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList();
OK stop here. If the ultimate goal of this query is to select only SubToSubMenu entities, you can replace this part with just
.Select(s => s.s);
...and ignore the rest of the subsequent statements.
However, you could also go one step further and make the association between the SubToSubMenu and MenuPermissions entities implicit in your EF configuration, so you'll have no need to .Join in LINQ. Given this, the eventual query should be similar to:
var ss = db.SubToSubMenus
.Where(stsm => stsm.Active
&& stsm.MenuPermissions.RoleId == roleId
&& stsm.MenuPermissions.HasPermission);
Try this:
var ss =
db
.SubToSubMenus
.Join(
db.MenuPermissions,
s => s.ID,
p => p.SubToSubMenuId,
(s, p) => new { s, p })
.Where(w => w.s.Active == true && w.p.RoleId == roleId && w.p.hasPermission == true)
.Select(s => new
{
ID = s.s.ID,
SubToSubMenuName = s.s.SubToSubMenuName,
Description = s.s.Description,
})
.ToList()
.Select(s => new SubToSubMenu()
{
ID = s.ID,
SubToSubMenuName = s.SubToSubMenuName,
Description = s.Description,
})
.ToList();
I have added a simple projection to the end of your query. This is to keep the code as close to how you had it to begin with and to aid with any future refactoring.
In this case, it can certainly be coded as a single projection.
I have a view model which contains a List<>. This list is a collection of another model and I'm trying to fill this list while filling an IEnumerable of my view model. While doing this I get the error “Only parameterless constructors and initializers are supported in LINQ to Entities”. The error came to be because of the Locations = new List<> part in which I try to fill the list. What I would like to know is how to fill this list the correct way.
Code:
IEnumerable<PickListLineViewModel> lineList = dbEntity.PickListLine
.Where(i => i.PickID == id && i.Status != "C")
.Select(listline => new PickListLineViewModel
{
ArticleName = dbEntity.Item
.Where(i => i.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(p => p.Description)
.FirstOrDefault(),
PickID = listline.PickID,
BaseDocID = listline.BaseDocID,
BaseDocType = listline.BaseDocType,
BaseLineNum = listline.BaseLineNum,
LineNum = listline.LineNum,
Quantity = listline.Quantity,
ReleasedByQty = listline.ReleasedByQty,
Status = listline.Status,
PickedQuantity = listline.PickedQuantity,
Locations = new List<BinLocationItemModel>(dbEntity.BinLocation_Item
.Where(t => t.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(locitem => new BinLocationItemModel
{
ItemId = locitem.ItemId,
Barcode = locitem.BinLocation.Barcode,
BinLocationCode = locitem.BinLocation.BinLocationCode,
BinLocationId = locitem.BinLocationId,
BinLocationItemId = locitem.ItemId,
StockAvailable = locitem.StockAvailable
}))
.ToList(),
ArticleID = dbEntity.Item
.Where(i => i.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(p => p.ItemCode)
.FirstOrDefault()
})
.AsEnumerable();
BinLocationItemModel:
public class BinLocationItemModel
{
[Required]
public int BinLocationItemId { get; set; }
public string Barcode { get; set; }
public string BinLocationCode { get; set; }
[Required]
public int BinLocationId { get; set; }
[Required]
public int ItemId { get; set; }
public decimal? StockAvailable { get; set; }
}
In your PickListLineViewModel constructor you should initialise your Locations list like below
public class PickListLineViewModel
{
public PickListLineViewModel()
{
Locations = new List<BinLocationItemModel>();
}
public List <BinLocationItemModel> Locations { get; set; }
}
public class BinLocationItemModel
{
....
}
Then for your linq query you should be able to do the following, you wont need your new list inside the linq:
Locations = dbEntity.BinLocation_Item
.Where(t => t.ItemId == dbEntity.SalesOrderLine
.Where(idb => idb.DocId == listline.BaseDocID &&
idb.DocType.Equals(listline.BaseDocType) &&
idb.LineNum == listline.BaseLineNum)
.Select(iid => iid.ItemId)
.FirstOrDefault())
.Select(locitem => new BinLocationItemModel
{
ItemId = locitem.ItemId,
Barcode = locitem.BinLocation.Barcode,
BinLocationCode = locitem.BinLocation.BinLocationCode,
BinLocationId = locitem.BinLocationId,
BinLocationItemId = locitem.ItemId,
StockAvailable = locitem.StockAvailable
})
.ToList(),
I have 2 tables Orders and Items and I am trying to query these 2 tables for statistics retrieval.
Orders has columns OrderID[PK], ItemID[FK], OrderStatus etc.
Items has columns ItemID[PK], ItemName, ItemPrice etc.
I am fetching list of orders based on date range and then I am returning their counts based on their status.
Below is my StatisticsResponse.cs to return the response.
public class StatisticsResponse
{
public int CancelledOrderCount { get; set; }
public int CompletedOrderCount { get; set; }
public int InProgressOrderCount { get; set; }
public int TotalOrders { get; set; }
public Dictionary<string,int> ItemOrders { get; set;}
}
This is how I am retrieving Orders between 2 dates.
var orders = _unitOfWork.OrderRepository
.GetMany(x => (x.OrderStatus == "Pending"
&& x.OrderDate.Value.Date >= dtStartDate
&& x.OrderDate.Value.Date < dtEndDate) ||
((x.OrderStatus == "Completed" || x.OrderStatus == "Cancelled")
&& x.DeliveryDate.Date >= dtStartDate || x.DeliveryDate.Date < dtEndDate) || (x.LastUpdated.Value.Date >= dtStartDate || x.LastUpdated.Value.Date < dtEndDate)).ToList();
if (orders != null)
{
return new StatisticsResponse()
{
TotalOrders = orders.Count(),
CancelledOrderCount = orders.Where(x => x.OrderStatus == "Cancelled").Count(),
CompletedOrderCount = orders.Where(x => x.OrderStatus == "Completed").Count(),
InProgressOrderCount = orders.Where(x => x.OrderStatus != "Completed" && x.OrderStatus != "Cancelled").Count()
}
}
Now, in the ItemOrders property, which is of type Dictionary<string,int>, I want to group each item with their name and count. I have ItemID in my orders list, and I would like to join 2 tables to get the name before storing.
I have tried to use GroupBy as below but am totally stuck on how to get the name for the Item after grouping
ItemOrders = new Dictionary<string, int>
{
orders.GroupBy(x=>x.ItemID)// Stuck here
}
I also read about GroupJoin but couldn't quite make sure whether it can fit in here.
Could someone please let me know how I can join these 2 tables to get their name based on their ID?
You can use something along this:
using System.Entity.Data; //to use Include()
...
Dictionary<string,int> itemOrders = dbContext.Orders.Include(o=> o.Item)
.GroupBy(o=> o.Item)
.ToDictionary(g => g.Key.Name, g => g.Count());
This is assuming:
There is a navigation property set up from Order to Item
Each Order has one Item
So, I was able to achieve this with GroupJoin through various online example as below:
if (orders != null)
{
var items = _unitOfWork.ItemRepository.GetAll();
var itemOrders = items.GroupJoin(orders,
item => item.ItemID,
ord => ord.ItemID,
(item, ordrs) => new
{
Orders = ordrs,
itemName = item.ItemName
});
StatisticsResponse statsResponse = new StatisticsResponse()
{
//...
};
foreach (var item in itemOrders)
{
statsResponse.ItemOrders.Add(item.itemName, item.Orders.Count());
}
return statsResponse;
}
Hello I have just started to try and understand Parallel Linq and with my first attempt I am having no success. I am using EF 4.0 and a repository pattern class that I created to query the data. I don't believe that the repository pattern is the problem but I could be mistaken.
The database that I have isn't setup the way that I would like it but hey I inherited the system. The code that I am having the problem with is below:
var gId = Sql.ToGuid(Request["ID"]);
var lOrdersGridList = new OrdersGridList(); //Class that only contains properties
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
.Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
grdMain.DataSource = lOrdersForContact.ToDataTable().DefaultView; //ToDataTable extension function converts the List Object to a datatable.
If I run the Code without AsParallel the code executes with no problem however, once I add AsParallel I receive the following error:
Also just in case you wanted to see this is the class that I am declaring as new for the Select Above:
public class OrdersGridList : EntityObject
{
public string ORDER_NUM { get; set; }
public Guid ORDER_ID { get; set; }
public Guid SHIPPING_ACCOUNT_ID { get; set; }
public string SHIPPING_ACCOUNT_NAME { get; set; }
public Guid SHIPPING_CONTACT_ID { get; set; }
public string SHIPPING_CONTACT_NAME { get; set; }
public string NAME { get; set; }
}
If I remove all of the relationships that are used to retrieve data in the select I don't receive any errors:
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
//SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
//SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
//SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
//SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
// .Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
I would be more than happy to give more information if required. Any help you can provide on using PLinq I would appreciate it.
To me it appears like the BaseRepository class creates some kind of LINQ to Entities query using the Find and Select parameters.
Using AsParellel is made for LINQ to Objects, where your code actually evaluates the expressions you pass. Other LINQ dialects, including LINQ to Entities, translate it into a different query language like SQL. The SQL server may do reasonable parallelisation itself.