How to write a expression for a linq to sql property? - linq-to-sql

My appologies upfront for the lengthy question. I made quite an effort to make my question as clear as possible in one go. Please bear with me. ;o) any help will be greatly appreciated!
I have the classes Branch and Text:
class Branch
int ID
Text WebDescription
and a bunch of other properties
class Text
int ID
string UK
string NL
string FR
string IT
and a bunch of other properties as well
I want to only display the ID of the branch and its description in the appropriate language. I want only one query (no extra round trips) which retrieves only two fields (not the whole object).
I found three solutions
Via the object model in the query
// good: no round trips
// good: clean sql
// bad: impossible to use the currentUserLanguage parameter
var lang = "NL";
var dbProject = new ProjectDataContext();
var query = from b in dbProject.GetTable<Branch>()
select new
{
b.ID,
WebDescription = b.WebDescriptionObject.NL // <-- problem
};
var text = query.First().WebDescription;
Via the object model after the query
// good: no round trips (eager loading of text object)
// good: possible to use the currentUserLanguage parameter
// bad: loads the *whole* branch and text object, not just two fields
var lang= "NL";
var dbProject = new ProjectDataContext();
var query = from b in dbProject.GetTable<Branch>()
select new
{
b.ID,
WebDescription = b.GetWebDescriptionAsString(lang)
};
var text = query.First().WebDescription;
Using an expression
// good: I have the feeling I am on the right track
// bad: This doesn't work :o( throws an exception
var lang= "NL";
var dbProject = new ProjectDataContext();
var query = from b in dbProject.GetTable<Branch>()
select new
{
b.ID,
WebDescription = b.GetWebDescriptionAsExpression(lang)
};
var text = query.First().WebDescription;
Here is code for the two methods GetWebDescriptionAsString and GetWebDescriptionAsExpression.
public string GetWebDescriptionAsString(string lang)
{
if (lang== "NL") return WebDescriptionObject.NL;
if (lang== "FR") return WebDescriptionObject.FR;
if (lang== "IT") return WebDescriptionObject.IT;
return WebDescriptionObject.UK;
}
public Expression<Func<Branch, string>> GetWebDescriptionAsExpression(string lang)
{
if (lang== "NL") return b => b.WebDescriptionObject.NL;
if (lang== "FR") return b => b.WebDescriptionObject.FR;
if (lang== "IT") return b => b.WebDescriptionObject.IT;
return b => b.WebDescriptionObject.UK;
}

Without really answering the question, the cleanest approach would be to change the Text structure into a more normalized form like:
Text
ID
TextTranslation
ID
TextID
Lang
TextValue
where each text has a number of translations, one for each language.
The query would become something like:
var q =
from branch in dbProject.Branches
join text in dbProject.Texts on branch.TextID = text.ID
join translation in dbProject.TextTranslations on text.ID = translation.TextID
where translation.Lang == lang
select new
{
branch.ID,
WebDescription = translation.TextValue
};
This approach has other advantages as well, for example adding a new language will not change the model structure.

This would be very easy to do if you used a stored procedure. Are you opposed to using SP's as a solution?

If a stored procedure works, then I am happy to use it.

Thank you for you prompt reply.
I made a quick attempt. The UDF was already there, I just didn't know how to use it. The performance dropped significantly. The first solution is 3 times faster. In my understanding, this approach would require extra round trips to the database. Is that correct?
var query = from b in dbProject.GetTable<Branch>()
select new
{
b.ID,
WebDescription = db.fGetText(b.WebDescriptionID, (currentUserLanguage))
};

Without understanding your whole problem
create a stored procedure like this:
CREATE PROCEDURE spGetTheTextINeed #Language char(2), #BranchID int
AS
/* I don't know how your database is structured so you need to write this */
SELECT MyText from MyTable WHERE Language=#Language and Branch=#BranchID
Then you need to add the sp to your DBML and then you can just call the sp you need with the appropriate parameters:
var query = myDataContext.spGetTheTextINeed("NL",[your branch number])
Dim str As String
str = query.MyText
The code above is not to be exact - I don't understand your full requirements but this should get you started.

Related

Include nested entity details but don't group by then when grouping by other fields

I working with Database first C# MVC, EF6, LINQ and JSon to try and pass data to both Highcharts and Google Maps for some of my reporting.
If I could add an image I would show you the relevant portion of my model, but sadly I need more reputation to do that...
The portion of the Entity Model I'm concentrating on right now is based on a central Docket that contains a BuildingCode as part of a one-to-many relationship to a building with and address and further relationship to the buildings polygons (for mapping). Dockets are also classified by one or more DocketTypes and thus there is a many-to-many relationship between Dockets and DocketTypes, which is not directly exposed to through the EF.
As an example a Docket which represents an investigation, could be related to the theft of a mobile phone in building A located on Campus X, not only was the cellphone stolen but the assailant also assaulted the victim in order to steal the mobile phone. So there are 2 DocketTypes here 1. Theft of mobile phone and 2. assault. Note: this is fictitious and for illustration purposes only .
One of my fundamental reports requires that I count how many docketTypes affect each building and each campus in a given period. When I display this I also need to show what the DocketTypes are.
I have no end of nightmare trying to find a way to get this right, I keep running into circular reference errors and needing to use explicit conversions when trying to model the data with LINQ so that I can pass a single nested object through JSON to the client side where displaying will occur.
In the below code I am told I need an Explicit conversion:
Cannot implicitly convert type 'Campus_Investigator.ViewModels.DocketTypeViewModel' to 'System.Collections.Generic.IEnumerable<Campus_Investigator.ViewModels.DocketTypeViewModel>'. An explicit conversion exists (are you missing a cast?)
var currentDocketQuery = from d in db.Dockets
from dt in d.DocketTypes
from bp in d.BuildingDetail.BuildingPolygons
where d.OccurrenceStartDate >= datetime && d.BuildingDetail.CampusName == Campus
select new CampusBuildingDocketTypeViewModel()
{
BuildingCode = d.BuildingDetail.BuildingCode,
BuildingName = d.BuildingDetail.BuildingName,
//BuildingPolygons = d.BuildingDetail.BuildingPolygons,
DocketTypes = new DocketTypeViewModel()
{
Category = dt.Category,
SubCategory = dt.SubCategory,
ShortDescription = dt.ShortDescription
}
};
I appreciate any ideas on how I can explicitly convert this or is that a better method I can use and avoid the circular reference error?
You included some redundant part in your query (which performs some inner join). The from bp in d.BuildingDetail.BuildingPolygons is joined in but then is not shown in the result. So it totally does not make sense. There may be duplicated elements in the result due to that. The from dt in d.DocketTypes is wrong joined in, although you need it in the result but because the DocketTypes is output per d in db.Dockets, so it's just simply queried like this:
var currentDocketQuery = from d in db.Dockets
where d.OccurrenceStartDate >= datetime && d.BuildingDetail.CampusName == Campus
select new CampusBuildingDocketTypeViewModel()
{
BuildingCode = d.BuildingDetail.BuildingCode,
BuildingName = d.BuildingDetail.BuildingName,
//BuildingPolygons = d.BuildingDetail.BuildingPolygons,
DocketTypes = d.DocketTypes
};
In fact I can see the commented line //BuildingPolygons = d.BuildingDetail.BuildingPolygons, so if you want to include that, it should also work.
If the DocketTypes has different type of d.DocketTypes, then you need a simple projection like this:
var currentDocketQuery = from d in db.Dockets
where d.OccurrenceStartDate >= datetime && d.BuildingDetail.CampusName == Campus
select new CampusBuildingDocketTypeViewModel()
{
BuildingCode = d.BuildingDetail.BuildingCode,
BuildingName = d.BuildingDetail.BuildingName,
//BuildingPolygons = d.BuildingDetail.BuildingPolygons,
DocketTypes = d.DocketTypes.Select(e => new DocketTypeViewModel()
{
Category = e.Category,
SubCategory = e.SubCategory,
ShortDescription = e.ShortDescription
})
};
I managed to solve this one by using the below. The major hassle with this is the circular referencing that exists in the model. When JSON serializes these, everything falls apart so it takes a lot of transforming to make sure that I only extract what I need. In this case grouped campus and building data (below includes the polygons which where only half commented out in the above) and then the include the detail of the DocketTypes that occurred at each building.
var datetime = DateTime.Now.AddDays(-30);
var campusDocket = from d in db.Dockets
where d.OccurrenceStartDate >= datetime && d.BuildingDetail.CampusName == Campus
group d by new { d.BuildingDetail.CampusName, d.BuildingDetail.BuildingCode, d.BuildingDetail.BuildingName } into groupdata
select new CampusBuildingDocketTypeViewModel
{
BuildingCode = groupdata.Key.BuildingCode,
BuildingName = groupdata.Key.BuildingName,
CampusName = groupdata.Key.CampusName,
Count = groupdata.Count(),
BuildingPolygons = from bp in db.BuildingPolygons
where bp.BuildingCode == groupdata.Key.BuildingCode
select new BuildingPolygonViewModel
{
Accuracy = bp.Accuracy,
BuildingCode = bp.BuildingCode,
PolygonOrder = bp.PolygonOrder,
Latitude = bp.Latitude,
Longitude = bp.Longitude
},
DocketTypes = from doc in db.Dockets
from dt in doc.DocketTypes
where doc.OccurrenceStartDate >= datetime && doc.BuildingCode == groupdata.Key.BuildingCode
select new DocketTypeViewModel
{
Category = dt.Category,
SubCategory = dt.SubCategory,
ShortDescription = dt.ShortDescription
}
};
The Answer again is ViewModels. I'm finding ViewModels seem to solve a lot of problems...

LINQ to SQL, in memory lists and anonymous types

I am joining a table to an in memory list and wanting to create an anonymous type from the results. I have joined the 2 data sources together ok, but I'm not sure of the syntax required to use a property from the in memory list in the anonymous type.
Here's my code
public DataKeys(IEnumerable<Element> elements)
{
var defsource = new DefinitionSource();
var items = from def in defsource.Definitions
where elements.Select(el=> el.Value).Contains(def.Name)
select new { def.Key };
...
}
(Obviously "elements" is an in-memory list of element and "DefinitionSource" is a wrapper around a table.) This works fine, but as you can see there is no property from the in-memory list of elements. I've tried this
var items = from def in defsource.Definitions
where elements.Select(el=> el.Value).Contains(def.Name)
from el in elements
where el.Value.Equals(def.Name)
select new { el.NodeType, def.Key };
but at run time it gens a "{"Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."}"
So what is the syntax I need to do this?
Many thx IA
Simon
This might help:
var def=defsource.Definitions
.Where(a=>elements.Select(el=>el.Value).Contains(a.Name))
.ToList();
var items = from d in def
from el in elements
.Where(a=>a.Value==d.Name)
select new { el.NodeType, d.Key };
To query over two data sources both of them needs to be in memory are both of them should be in database. you can do the following to achieve the results you want
var query = (from def in defsource.Definitions
where elements.Select(el=> el.Value).Contains(def.Name)
).ToList();//bring filtered result in memory
var Items = from def in query //Definitions are already filtered no need to re-apply where
from el in elements.Where(a=>a.Value == def.Name)
select new {el.NodeType, def.Key};

How do I retrieve Insert Statements without using SubmitChanges in LINQPad

I have something similar to the code below in LINQPAD using C# Statements. My goal is to get the actual SQL Insert statments not actually update the database.
I can easily delete the data after it inserts with this small sample, but I will need this for a larger push of data. I hope that I have missed something simple either in L2S or LINQPad.
Is there an easier way retrieve the SQL Insert?
var e1 = new MyEntity(){ Text = "First" };
var e2 = new MyEntity(){ Text = "Second" };
MyEntities.InsertOnSubmit(e1);
MyEntities.InsertOnSubmit(e2);
SubmitChanges();
A quick-n-dirty way is to wrap everything in a transaction scope that is never commited:
using(TransactionScope ts = new TransactionScope())
{
var e1 = new MyEntity(){ Text = "First" };
var e2 = new MyEntity(){ Text = "Second" };
MyEntities.InsertOnSubmit(e1);
MyEntities.InsertOnSubmit(e2);
SubmitChanges();
// Deliberately not committing the transaction.
}
This works well for small volumes. If the data volume is large and you have full recovery model on the database the transaction log growth might become a problem.
When we did the samples for "LINQ in Action", we used the following method which gets the scheduled changes from the context:
public String GetChangeText(System.Data.Linq.DataContext context)
{
MethodInfo mi = typeof(DataContext).GetMethod("GetChangeText",
BindingFlags.NonPublic | BindingFlags.Instance);
return mi.Invoke(context, null).ToString();
}
If you want to see this in action, download the samples in LINQPad (see http://www.thinqlinq.com/Default/LINQ-In-Action-Samples-available-in-LINQPad.aspx) and check out chapter 6 example 6.29.

AS3 How to make a kind of array that index things based on a object? but not being strict like dictionary

How to make a kind of array that index things based on a object? but not being strict like dictionary.
What I mean:
var a:Object = {a:3};
var b:Object = {a:3};
var dict:Dictionary = new Dictionary();
dict[a] = 'value for a';
// now I want to get the value for the last assignment
var value = dict[b];
// value doesn't exits :s
How to make something like that. TO not be to heavy as a lot of data will be flowing there.
I have an idea to use the toString() method but I would have to make custom classes.. I would like something fast..
Why not make a special class that encapsulates an array, put methods in there to add and remove elements from the array, and then you could make a special method (maybe getValueByObject(), whatever makes sense). Then you could do:
var mySpecialArrayClass:MySpecialArrayClass = MySpecialArrayClass();
var a:Object = {a:3};
var b:Object = {a:3};
mySpecialArrayClass.addElement(a,'value for a');
var value = mySpecialArrayClass.getValueByObject(a);
I could probably cook up a simple example of such a class if you don't follow.
Update:
Would something like this help?
http://snipplr.com/view/6494/action-script-to-string-serialization-and-deserialization/
Update:
Could you use the === functionality? if you say
if ( object === object )
it compares the underlying memory address to see if two objects are the same reference...

Linq-to-SQL with a table valued UDF user defined function

I am new to Linq and trying to get a handle on how to bind a drop down to a SQL user defined function.
//Populate the Pledge dropdown
var db = new App_Data.MyDBDataContext();
int? partnerID = Convert.ToInt32(Request.QueryString["PartnerID"]);
var pledges =
from p in db.ufn_AvailablePledgesByPartner(partnerID)
select new
{
PledgeAndPartnerName = p.PledgeAndPartnerName,
PledgeID = p.PledgeID
};
DropDownList ddlPledgeID = (DropDownList)DetailsViewContribution.FindControl("DropDownListPledgeID");
ddlPledgeID.DataSource = pledges;
ddlPledgeID.DataTextField = pledges.PledgeAndPartnerName;
ddlPledgeID.DataValueField = pledges.PledgeID;
The current problem is the last 2 lines where I'm trying to reference properties of the anonymous class. "'System.Linq.IQueryable' does not contain a definition for 'PledgeAndPartnerName' and no extension method..." I naively thought the compiler was supposed to figure this out, but obviously I'm assuming C# is now more dynamic than it really is.
Thanks for any input.
Try this:
ddlPledgeID.DataTextField = "PledgeAndPartnerName";
ddlPledgeID.DataValueField = "PledgeID";