Linq to XML elements and descendants in the same search - linq

Still learning Linq and had a problem trying to retrieve and element as well as descendants of another in the same select. I searched for a solution, but could not find what I was looking for and came up with a solution. But is it the right way to do it? Somehow, although it works, it does feel right.
I have the following XML structure:
<Tables>
<Table>
<SourceTable>WrittenRecordsTable</SourceTable>
<Researcher>Fred Blogs</Researcher>
<QuickRef>cwr</QuickRef>
<TableType>WrittenRecords</TableType>
<FieldMapping>
<RecordID>ID</RecordID>
<StartYear>StartYear</StartYear>
<EndYear>EndYear</EndYear>
<LastName>LastName</LastName>
<Title>Title</Title>
<Subject>Subject</Subject>
<Description>Reference</Description>
</FieldMapping>
</Table>
</Tables>
and the following Linq to XML:
var nodes = (from n in xml.Descendants("FieldMapping")
select new
{
SourceTable = (string)n.Parent.Element("SourceTable").Value,
RecordID = (string)n.Element("RecordID").Value,
StartYear = (string)n.Element("StartYear").Value,
EndYear = (string)n.Element("EndYear").Value,
LastName = (string)n.Element("LastName").Value,
Title = (string)n.Element("Title").Value,
Subject = (string)n.Element("Subject").Value
}).ToList();
It is the way I retrieve the SourceTable element that feels wrong. Am I worrying too much or is there a better way? Also, is it better to work with c# expressions rather than queries?

If your document structure always has to contain those nodes your query (and accessing SourceTable) is fine. It's fairly obvious of what's going on given reader knows how XML looks like.
However, if you want more top-down approach (which might seem more natural and easier to grasp), you can always query Table node first and store FieldMapping in a variable, but I wouldn't say it has any advantages over your approach:
var nodes = (from table in doc.Descendants("Table")
let fieldMapping = table.Element("FieldMapping")
select new
{
SourceTable = (string)table.Element("SourceTable").Value,
RecordID = (string)fieldMapping.Element("RecordID").Value,
StartYear = (string)fieldMapping.Element("StartYear").Value,
EndYear = (string)fieldMapping.Element("EndYear").Value,
LastName = (string)fieldMapping.Element("LastName").Value,
Title = (string)fieldMapping.Element("Title").Value,
Subject = (string)fieldMapping.Element("Subject").Value
}).ToList();

Related

filter based on parent attribute

I am looking for some help with buliding a LINQ query. i want to get all the Tabs for a particular datatype. I started builind the query but seems like still having some issue. Appreciate any feedback.Thanks Jay
<DataType name="WELL_INDUSTRY">
<Database key1="key1" key2="" delimeter="">
<Tabs>
<Tab>
<Name>Basic</Name>
var tabs = from tab in doc.Descendants("Tab")
where tab.Parent.Parent.Attribute("Name").ToString() == "WELL_INDUSTRY"
select new
{
Name = tab.Descendants("Name").First().Value
};
foreach (var tab in tabs)
Debug.WriteLine(tab.Name);
I would say, you're starting from the wrong side of the problem. Why don't you find <DataType> first, and then select all containing <Tab>s?
var tabs = from dt in doc.Descendants("DataType")
where (string)dt.Attribute("Name") == "WELL_INDUSTRY"
from tab in dt.Elements("Database").Elements("Tabs").Elements("Tab")
select new
{
Name = (string)tab.Elements("Name").First()
};
I would also recommend replacing doc.Descendants("DataType") with more strict match using Element/Elements with proper element names. It will make the query faster. I can't do that for you because I don't know whole document structure.

Is it possible to break up an element itself before another join?

I have got two xml documents, simplified as
<NumSetA>
<num Operation="+/-">1</num>
<num Operation="+">3</num>
<num Operation="+/*">4</num>
</NumSetA>
<NumSetB>
<num>2</num>
<num>9</num>
</NumSetB>
I want to join NumSetA with NumSetB with the possible operations stated in the Operation tag, ie.
1+2, 1-2, 1+9, 1-9, 3+2, 3+9, 4+2, 4+9, 4*2, 4*9
by using string.split('/')
What I want to do is
var CrossJoin = SetA.Elements("num").join(this.attribute("Operation").value.split('/'),
.join(SetB.Elements("num"))
Sorry for being inventive. Hope you understand what I am saying.
How can I achieve that?
It's pretty easy to do with the query syntax:
var crossJoin =
from numA in SetA.Elements("num")
from op in numA.Attribute("Operation").value.split('/')
from numB in SetB.Elements("num")
select new {
a = numA.value,
op,
b = numB.value
};

What is the performance optimum (or even better coding practise) for writing this Linq query

I am new to linq so please excuse me if I am asking a very basic question:
paymentReceiptViewModel.EntityName = payment.CommitmentPayments.First().Commitment.Entity.GetEntityName();
paymentReceiptViewModel.HofItsId = payment.CommitmentPayments.First().Commitment.Entity.ResponsiblePerson.ItsId;
paymentReceiptViewModel.LocalId = payment.CommitmentPayments.First().Commitment.Entity.LocalEntityId;
paymentReceiptViewModel.EntityAddress = payment.CommitmentPayments.First().Commitment.Entity.Address.ToString();
This code is too repetitive and I am sure there is a better way of writing this.
Thanks in advance for looking this up.
Instead of executing query at each line, get commitment entity once:
var commitment = payment.CommitmentPayments.First().Commitment.Entity;
paymentReceiptViewModel.EntityName = commitment.GetEntityName();
paymentReceiptViewModel.HofItsId = commitment.ResponsiblePerson.ItsId;
paymentReceiptViewModel.LocalId = commitment.LocalEntityId;
paymentReceiptViewModel.EntityAddress = commitment.Address.ToString();
It depends a bit on what you are selecting to, you cannot select from one entity into another in Linq to Entities. If you are using LINQ to SQL and creating the paymentReceiptModel, you can do this.
var paymentReceiptModel = payment.CommitmentPayments.select(x=>new{
EntityName = x.Commitment.Entity.GetEntityName(),
HofItsId = x.Commitment.Entity.ResponsiblePerson.ItsId,
LocalId = x.Commitments.Entity.LocalEntityId,
EntityAddress = x.Commitment.Entity.Address
}).FirstOrDefault();
If you are using an already instantiated paymentReceiptModel and just need to assign properties then you are better looking to the solution by lazyberezovsky.
To get around the limitation in Linq to Entities, if that is what you are using, you could do this
var result = payment.CommitmentPayments.select(x=>x);
var paymentReceiptModel= result.select(x=>new
{
EntityName = x.Commitment.Entity.GetEntityName(),
HofItsId = x.Commitment.Entity.ResponsiblePerson.ItsId,
LocalId = x.Commitments.Entity.LocalEntityId,
EntityAddress = x.Commitment.Entity.Address
}).FirstOrDefault();
This essentially, makes the majority of your query Linq to Objects, only the first line is Linq to Entities

Linq: Dynamic Query Contruction: query moves to client-side

I've been following with great interest the converstaion here:
Construct Query with Linq rather than SQL strings
with regards to constructing expression trees where even the table name is dynamic.
Toward that end, I've created a Extension method, addWhere, that looks like:
static public IQueryable<TResult> addWhere<TResult>(this IQueryable<TResult> query, string columnName, string value)
{
var providerType = query.Provider.GetType();
// Find the specific type parameter (the T in IQueryable<T>)
var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
var tableType = iqueryableT.GetGenericArguments()[0];
var tableName = tableType.Name;
var tableParam = Expression.Parameter(tableType, tableName);
var columnExpression = Expression.Equal(
Expression.Property(tableParam, columnName),
Expression.Constant(value));
var predicate = Expression.Lambda(columnExpression, tableParam);
var function = (Func<TResult, Boolean>)predicate.Compile();
var whereRes = query.Where(function);
var newquery = whereRes.AsQueryable();
return newquery;
}
[thanks to Timwi for the basis of that code]
Which functionally, works.
I can call:
query = query.addWhere("CurUnitType", "ML 15521.1");
and it's functionally equivalent to :
query = query.Where(l => l.CurUnitType.Equals("ML 15521.1"));
ie, the rows returned are the same.
However, I started watching the sql log, and I noticed with the line:
query = query.Where(l => l.CurUnitType.Equals("ML 15521.1"));
The Query generated is:
SELECT (A bunch of columns)
FROM [dbo].[ObjCurLocView] AS [t0]
WHERE [t0].[CurUnitType] = #p0
whereas when I use the line
query = query.addWhere("CurUnitType", "ML 15521.1");
The query generated is :
SELECT (the same bunch of columns)
FROM [dbo].[ObjCurLocView] AS [t0]
So, the comparison is now happening on the client side, instead of being added to the sql.
Obviously, this isn't so hot.
To be honest, I mostly cut-and-pasted the addWhere code from Timwi's (slightly different) example, so some of it is over my head. I'm wondering if there's any adjustment I can make to this code, so the expression is converted into the SQL statement, instead of being determined client-side
Thanks for taking the time to read through this, I welcome any comments, solutions, links, etc, that could help me with this. And of course if I find the solution through other means, I'll post the answer here.
Cheers.
The big problem is that you're converting the expression tree into a delegate. Look at the signature of Queryable.Where - it's expressed in expression trees, not delegates. So you're actually calling Enumerable.Where instead. That's why you need to call AsQueryable afterwards - but that doesn't do enough magic here. It doesn't really put it back into "just expression trees internally" land, because you've still got the delegate in there. It's now wrapped in an expression tree, but you've lost the details of what's going on inside.
I suspect what you want is this:
var predicate = Expression.Lambda<Func<TResult, Boolean>>
(columnExpression, tableParam);
return query.Where(predicate);
I readily admit that I haven't read the rest of your code, so there may be other things going on... but that's the core bit. You want a strongly typed expression tree (hence the call to the generic form of Expression.Lambda) which you can then pass into Queryable.Where. Give it a shot :)

Linq filter collection with EF

I'm trying to get Entity Framework to select an object and filter its collection at the same time. I have a JobSeries object which has a collection of jobs, what I need to do is select a jobseries by ID and filter all the jobs by SendDate but I can't believe how difficult this simple query is!
This is the basic query which works:
var q = from c in KnowledgeStoreEntities.JobSeries
.Include("Jobs.Company")
.Include("Jobs.Status")
.Include("Category")
.Include("Category1")
where c.Id == jobSeriesId
select c;
Any help would be appreciated, I've been trying to find something in google and what I want to do is here:http://blogs.msdn.com/bethmassi/archive/2009/07/16/filtering-entity-framework-collections-in-master-detail-forms.aspx
It's in VB.NET though and I couldn't convert it to C#.
EDIT: I've tried this now and it doesn't work!:
var q = from c in KnowledgeStoreEntities.JobSeries
.Include("Jobs")
.Include("Jobs.Company")
.Include("Jobs.Status")
.Include("Category")
.Include("Category1")
where (c.Id == jobSeriesId & c.Jobs.Any(J => J.ArtworkId == "13"))
select c;
Thanks
Dan
Include can introduce performance problems. Lazy loading is guaranteed to introduce performance problems. Projection is cheap and easy:
var q = from c in KnowledgeStoreEntities.JobSeries
where c.Id == jobSeriesId
select new
{
SeriesName = c.Name,
Jobs = from j in c.Jobs
where j.SendDate == sendDate
select new
{
Name = j.Name
}
CategoryName = c.Category.Name
};
Obviously, I'm guessing at the names. But note:
Filtering works.
SQL is much simpler.
No untyped strings anywhere.
You always get the data you need, without having to specify it in two places (Include and elsewhere).
No bandwith penalties for retrieving columns you don't need.
Free performance boost in EF 4.
The key is to think in LINQ, rather than in SQL or in materializing entire entities for no good reason as you would with older ORMs.
I've long given up on .Include() and implemented Lazy loading for Entity Framework

Resources