Linq-2-Sql code: Does this scale? - linq-to-sql

I'm just starting to use linq to sql. I'm hoping that someone can verify that linq-2-sql has deferred execution until the foreach loop is executed. Over all, can someone tell me if this code scales. It's a simple get method with a few search parameters. Thanks!
Code:
public static IList<Content> GetContent(int contentTypeID, int feedID, DateTime? date, string text)
{
List<Content> contentList = new List<Content>();
using (DataContext db = new DataContext())
{
var contentTypes = db.ytv_ContentTypes.Where(p => contentTypeID == -1 || p.ContentTypeID == contentTypeID);
var feeds = db.ytv_Feeds.Where(p => p.FeedID == -1 || p.FeedID == feedID);
var targetFeeds = from f in feeds
join c in contentTypes on f.ContentTypeID equals c.ContentTypeID
select new { FeedID = f.FeedID, ContentType = f.ContentTypeID };
var content = from t in targetFeeds
join c in db.ytv_Contents on t.FeedID equals c.FeedID
select new { Content = c, ContentTypeID = t.ContentType };
if (String.IsNullOrEmpty(text))
{
content = content.Where(p => p.Content.Name.Contains(text) || p.Content.Description.Contains(text));
}
if (date != null)
{
DateTime dateTemp = Convert.ToDateTime(date);
content = content.Where(p => p.Content.StartDate <= dateTemp && p.Content.EndDate >= dateTemp);
}
//Execution has been defered to this point, correct?
foreach (var c in content)
{
Content item = new Content()
{
ContentID = c.Content.ContentID,
Name = c.Content.Name,
Description = c.Content.Description,
StartDate = c.Content.StartDate,
EndDate = c.Content.EndDate,
ContentTypeID = c.ContentTypeID,
FeedID = c.Content.FeedID,
PreviewHtml = c.Content.PreviewHTML,
SerializedCustomXMLProperties = c.Content.CustomProperties
};
contentList.Add(item);
}
}
//TODO
return contentList;
}

Depends on what you mean with 'scales'. DB side this code has the potential of causing trouble if you are dealing with large tables; SQL Server's optimizer is really poor at handling the "or" operator in where clause predicates and tend to fall back to table scans if there are multiple of them. I'd go for a couple of .Union calls instead to avoid the possibility that SQL falls back to table scans just because of the ||'s.
If you can share more details about the underlying tables and the data in them, it will be easier to give a more detailed answer...

Related

Report don't display any data

I'm working on D365FO. I did create a report added a query to dataset that is based on a tmp table.
Now when I try to generate the report I get just the precisiondesign. No data is shown in it. How can I fix this ?
I think this is problem is somewhere outside of my code because I tried to generate a already created report and had the same problem.
[SrsReportParameterAttribute(classStr(ProductionStatusContract))]
class ProductionStatusDP extends SrsReportDataProviderBase
{
ProductionStatusTmp ProductionStatusTmp;
ProdTable prodtable;
SalesTable salestable;
InventDim inventdim;
SalesLine salesline;
public void processReport()
{
ProductionStatusContract contract = this.parmDataContract() as
ProductionStatusContract;
date FromDate;
date ToDate;
boolean DateBetween = false;
if(contract.parmToDate() && contract.parmFromDate())
{
ToDate = contract.parmToDate();
FromDate = contract.parmFromDate();
DateBetween = true;
}
super();
delete_from ProductionStatusTmp;
ProductionStatusTmp.clear();
while select prodtable where prodtable.CollectRefLevel == 0
join inventdim where prodtable.InventDimId == inventdim.inventDimId
join salesline where prodtable.InventRefType == salesline.InventRefType
&& prodtable.InventRefId == salesline.SalesId
&& prodtable.InventRefTransId == salesline.InventTransId
&& prodtable.InventRefType == inventreftype::Sales
{
ProductionStatusTmp.clear();
Info(strFmt("%1", prodtable.ProdId));
ProductionStatusTmp.AcceptedDate = prodtable.CreatedDateTime;
ProductionStatusTmp.ProdWeek = wkOfYr(prodtable.CreatedDateTime) + year(prodtable.CreatedDateTime);
ProductionStatusTmp.ExternalNum = salesline.ExternalItemId;
ProductionStatusTmp.Progress = ((prodtable.qtycalc * 100) / prodtable.QtySched);
ProductionStatusTmp.Quantity = prodtable.QtyCalc;
ProductionStatusTmp.AcceptedBy = prodtable.CreatedBy;
ProductionStatusTmp.ProdItemId = InventDim.InventLocationId;
ProductionStatusTmp.Ware = prodtable.Name;
ProductionStatusTmp.ProductionStatus = prodtable.ProdStatus;
ProductionStatusTmp.Produced = prodtable.QtySched;
ProductionStatusTmp.insert();
}
}
[SrsReportDataSetAttribute(tableStr(ProductionStatusTmp))]
public ProductionStatusTmp getProductionStatusTmp()
{
select ProductionStatusTmp;
return ProductionStatusTmp;
}
}
You will have to verify that your report does indeed display the data you provide.
You can do this by providing fixed static data in your data provider to see if shows up in the SSRS report preview.
See this video for an example to see how.

Dealing with nested for loops in Swift for a JSON file

One of the JSON requests that I make returns a file with a bunch of nested information. The format is roughly as follows groups->individual group->teams in group.
Currently I am dealing with this with a nested for loop where I look at the outer groups and then run the inner loop to get the information for the individual teams.
I've uploaded a copy of the JSON file to paste bin, here is the link. http://pastebin.com/D14wYDEs. This particular example doesn't have that many groups and teams but its possible to have way more, which makes the concept of nested for loops seem impractical.
I was wondering if somebody had a suggestion as to a better system of doing this, or any suggestion really.
Heres my current code:
func generateTablaDePosiciones() {
estadisticaUtilizada = 3
var tablaDePosicionesJSON = getJSONStats(3,tkn,eqID)
//checks to see that contents != nil, meaning the JSON file was found
if tablaDePosicionesJSON != nil {
tablaDePosicionesArray.removeAll(keepCapacity: false)
var numeroDeGruposEnTablaDePosiciones = tablaDePosicionesJSON["grupos"].count
for var index = 0; index < numeroDeGruposEnTablaDePosiciones; ++index {
var grupo = tablaDePosicionesJSON["grupos"][index]["grupo"].string
var etiqueta1 = tablaDePosicionesJSON["grupos"][index]["etiqueta-1"].string
var etiqueta2 = tablaDePosicionesJSON["grupos"][index]["etiqueta-2"].string
var etiqueta3 = tablaDePosicionesJSON["grupos"][index]["etiqueta-3"].string
var etiqueta4 = tablaDePosicionesJSON["grupos"][index]["etiqueta-4"].string
var etiqueta5 = tablaDePosicionesJSON["grupos"][index]["etiqueta-5"].string
var preTablaDePosicionesNuevo = preTablaDePosiciones(grupo: grupo!, etiqueta1: etiqueta1!, etiqueta2: etiqueta2!, etiqueta3: etiqueta3!, etiqueta4: etiqueta4!, etiqueta5: etiqueta5!)
preTablaDePosicionesArray.append(preTablaDePosicionesNuevo)
numeroDeTablaDePosiciones = tablaDePosicionesJSON["grupos"][index]["lista-body"].count
for(var innerIndex = 0; innerIndex < numeroDeTablaDePosiciones; ++innerIndex) {
var rank = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["rank"].string
var equipoID = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["equipoID"].number! as Int
var nomEquipo = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["nom-equipo"].string
var d1 = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["d1"].string
var d2 = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["d2"].string
var d3 = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["d3"].string
var d4 = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["d4"].string
var d5 = tablaDePosicionesJSON["grupos"][index]["lista-body"][innerIndex]["d5"].string
var tablaDePosicionesNuevo = tablaDePosiciones(rank: rank!, equipoID: equipoID, nomEquipo: nomEquipo!, d1: d1!, d2: d2!, d3: d3!, d4: d4!, d5: d5!)
tablaDePosicionesArray.append(tablaDePosicionesNuevo)
}
}
} else {
estadisticaUtilizada = 0
println("Tabla de Posiciones JSON was nil")
}
}
I would use a while loop. Increment an index with each execution and then dynamically construct the key using that index. Collect your results in an array and then pass on that array instead of each individual object.
Also, you should really be unwrapping all these values as you're parsing them instead of force unwrapping (!). In this while loop you can use a conditional binding while let to handle that, and if it fails—i.e. it found no value for that key—it will exit.
Something like this:
var index = 1
var results = [String]()
while let etiqueta = grupo["etiqueta-\(index)"] as? String {
results.append( etiqueta )
index++
}
let preTablaDePosicionesNuevo = preTablaDePosiciones(grupo: name, etiquetas: results)
preTablaDePosicionesArray.append( preTablaDePosicionesNuevo )

In MVC, pass complex query and view model to session?

I have a view model:
public class UserCollectionView
{
public CardCollection CardCollections { get; set; }
public Card Cards { get; set; }
}
I have a List View Controller:
public ActionResult ViewCollection(int? page)
{
var userid = (int)WebSecurity.CurrentUserId;
var pageNumber = page ?? 1;
int pageSize = 5;
ViewBag.OnePageOfCards = pageNumber;
if (Session["CardCollection"] != null)
{
var paging = Session["CardCollection"].ToString.();
return View(paging.ToPagedList(pageNumber, pageSize));
}
var viewModel = from c in db.Cards
join j in db.CardCollections on c.CardID equals j.CardID
where (j.NumberofCopies > 0) && (j.UserID == userid)
orderby c.Title
select new UserCollectionView { Cards = c, CardCollections = j };
Session["CardCollection"] = viewModel;
return View(viewModel.ToPagedList(pageNumber, pageSize));
I am trying to use the PagedList to add paging to the results. I have been able to do this when I am not using a query that returns data from 2 databases in a single view. As shown here
My end result looks something like this:
Cards.SeveralColumns CardCollections.ColumnA CardCollections.ColumnB
Row 1 Data from Cards Table A from CardCollections B from CardCollections
Row 2 Data from Cards Table A from CardCollections B from CardCollections
Row 3 Data from Cards Table A from CardCollections B from CardCollections
And so on... I get an error
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I have tried variations of SQL statements but can't get it to fit with my view model. In SQL Management Studio this brings back the correct results
Select * from Cards Inner Join CardCollections On Cards.CardID = CardCollections.CardID where CardCollections.UserID = 1 and CardCollections.NumberofCopies > 0;
I need a way to pass the query in session so the paging will operate correctly. Any advice is appreciated. :)
Short answer is, you can't. The model needs to be a snapshot of the content and therefore you can't pass an open query across the boundary (either as a hand-off to a session or to the client directly).
What you're seeing is the disposal of the context beyond it's initial use (where you assemble var viewmodel).
With that said, you can cache the results (to save overhead) if querying the data is an expensive operation. Basically, you'd store the entire collection (or at least a large subset of the collection) in the session/memorycache (which can then be manipulated into a paged list). Something to the effect of:
public ActionResult ViewCollection(int? page)
{
var userId = (int) WebSecurity.CurrentUserId;
var pageNumber = page ?? 1;
var pageSize = 5;
ViewBag.OnePageOfCards = pageNumber;
var cacheKey = String.Format("ViewCollection[{0}]", userId);
var entities = GetOrCreateCacheItem<IEnumerable<UserCollectionView>>(cacheKey, () => {
var query = (from c in db.Cards
join j in db.CardCollections on c.CardID equals j.CardID
where (j.NumberofCopies > 0) && (j.UserID == userid)
orderby c.Title
select new UserCollectionView { Cards = c, CardCollections = j }
)
.ToList(); // Force fetch from Db so we can cache
});
return View(entities.ToPagedList(pageNumber, pageSize));
}
// To give an example of a cache provider; Feel free to change this,
// abstract it out, etc.
private T GetOrCreateCacheItem<T>(string cacheKey, Func<T> getItem)
{
T cacheItem = MemoryCache.Default.Get(cacheKey) as T;
if (cacheItem == null)
{
cacheItem = getItem();
var cacheExpiry = DateTime.Now.AddMinutes(5);
MemoryCache.Default.Add(cacheKey, cacheItem, cacheExpiry);
}
return cacheItem;
}
It turns out that I didn't need to pass the query at all. If I let it run through it works fine without the session. I am not really sure why this works but my search query has to be passed. Maybe it is because I am using a viewmodel to perform the query. I will experiment and post if I find anything. Currently the working code is:
public ActionResult ViewCollection(int? page)
{
var userid = (int)WebSecurity.CurrentUserId;
var pageNumber = page ?? 1;
int pageSize = 5;
ViewBag.OnePageOfCards = pageNumber;
ViewBag.Rarity_ID = new SelectList(db.Rarities, "RarityID", "Title");
ViewBag.MainType_ID = new SelectList(db.MainTypes, "MainTypeID", "Title");
ViewBag.CardSet_ID = new SelectList(db.CardSets, "CardSetID", "Title");
ViewBag.SubType_ID = new SelectList(db.SubTypes, "SubTypeID", "Title");
var viewModel = from c in db.Cards
join j in db.CardCollections on c.CardID equals j.CardID
where (j.NumberofCopies > 0) && (j.UserID == userid)
orderby c.Title
select new UserCollectionView { Cards = c, CardCollections = j };
return View(viewModel.ToPagedList(pageNumber, pageSize));

query not displaying if another table has null value(sequence contains no elements)

I have a problem with the following code. It displays details if all tables have at least one value, but nothing if at least one table does not have a value. I get the message
sequence contains no elements.
My code as follow:
public sneakerDetails GetSneakerDetails(int id)
{
IQueryable<sneakerDetails> query = from sneaks in _context.sneakers
from image in _context.sneakerImages
from website in _context.sneakerWebsites
// from website in _context.sneakerWebsites
where sneaks.sneaker_id == id && image.sneaker_id == website.sneaker_id && sneaks.sneaker_id == website.sneake_id
select new sneakerDetails
{
//sneaker_id = sneaks.sneaker_id,
Colorway = sneaks.Colorway,
Name = sneaks.Name,
description = sneaks.description,
imageAlternative = image.imageAlternative,
release_date = sneaks.release_date,
imageB = image.imageB,
imageF = image.imageF,
imageL = image.imageL,
imageR = image.imageR,
website = website.website,
websiteLogo = website.websiteLogo
};
return query.ToList().First();
I have tried to change the return value to FirstOrDefault but when i click a particular sneaker it displays only the title and no data.
Do i need to write an if statement for both tables?
Try with DefaultIfEmpty():
from sneaks in _context.sneakers.DefaultIfEmpty()
from image in _context.sneakerImages.DefaultIfEmpty()
from website in _context.sneakerWebsites.DefaultIfEmpty()
// from website in _context.sneakerWebsites
where sneaks.sneaker_id == id
&& image.sneaker_id == website.sneaker_id
&& sneaks.sneaker_id == website.sneake_id
select new sneakerDetails
{
//sneaker_id = sneaks.sneaker_id,
Colorway = sneaks.Colorway,
Name = sneaks.Name,
description = sneaks.description,
imageAlternative = image.imageAlternative,
release_date = sneaks.release_date,
imageB = image.imageB,
imageF = image.imageF,
imageL = image.imageL,
imageR = image.imageR,
website = website.website,
websiteLogo = website.websiteLogo
};

Error msg on linq to dictionary-"an item of the same key value has already been added"

I'm trying do make a collection using linq based on ID which is a GUID.On using dictionary I'm getting error "An item with same key has already been added" Any suggestion?
foreach (Guid i in ar)
{
var prod = (from r in datacontext.item_tables's where r.itemID == i select r);
Dictionary<Guid, item_tables> tempdata =prod.ToDictionary(s => s.itemID);
Facet[] ftemp = new Facet[tempdata.Count];
string s1 = "";
ftemp[0] = new Facet("descriptiob", FacetType.Text, tempdata[i].Description);
ftemp[1] = new Facet("date", FacetType.Text, tempdata[i].uploaddate);
for (int iv = 0; iv < tempdata.Count; iv++)
{
s1 += tempdata[i].ProductName + " \n";
}
ftemp[2] = new Facet("ProductName", FacetType.Text, s1);
}
You're basically building your dictionary using this code:
datacontext
.item_tables
.Where(r => r.itemID == i)
.ToDictionary(s => s.itemID);
The Where is filtering the results to where itemID is a particular i (Guid) at a time, but can return zero, one, or more, results. Clearly the error you are getting says that for at least one value of i you are getting more than one record returned.
This means the issue either that your database isn't properly normalized or that you've got a logic error in your code.
It sounds like the latter to me.
Further down in your code you have this loop:
for (int iv = 0; iv < tempdata.Count; iv++)
{
s1 += tempdata[i].ProductName + " \n";
}
This says to me that you're expecting the tempdata dictionary to have more than one value - but you're building the dictionary using itemID as the key and this should only put one value in the dictionary based on your query. So something is wrong here in your logic.
Can you describe in more detail what you're trying to do?
Based on your comment below (without the clarification from my comment) this appears to be what you want:
var query =
from r in datacontext.item_tables
group r.ProductName by new
{
r.itemID,
r.Description,
r.uploaddate,
r.ItemName,
};
foreach (var items in query.ToArray())
{
var f0 = new Facet("descriptiob", FacetType.Text, items.Key.Description);
var f1 = new Facet("date", FacetType.Text, items.Key.uploaddate);
var f2 = new Facet("ProductName", FacetType.Text, String.Join("\n", items));
collection.AddItem(items.Key.ItemName, null, null, f0, f1, f2);
}