Can I 'flatten' an XDocument with Linq? - linq

I have a heavily nested XML document that I need to load into my db for additional processing. For various reasons beyond the scope of this discussion I need to 'flatten' that structure down, then load it into a DataTables and then I can SQLBulkCopy it into the db where it will get processed. So assume my original XML looks something like this (mine is even more heavily nested, but this is the basic idea):
<data>
<report id="1234" name="XYZ">
<department id="234" name="Accounting">
<item id="ABCD" name="some item">
<detail id="detail1" value="1"/>
<detail id="detail2" value="2"/>
<detail id="detail3" value="3"/>
</item>
</department>
</report>
</data>
and I want to flatten that down into a single (albeit redundant) table structure where each attribute becomes a column (i.e. ReportId, ReportName, DepartmentId, DepartmentName, ItemId, ItemName, Detail1, Detail2, Detail3).
So my question is simply 'is it possible to accomplish this with a simple Linq query'? In the past I would just write some XSLT and be done with it but I'm curious if the Linq library can accomplish the same thing?
thanks!

Is this what you're looking for?
var doc = XDocument.Load(fileName);
var details =
from report in doc.Root.Elements("report")
from department in report.Elements("department")
from item in department.Elements("item")
from detail in item.Elements("detail")
select new
{
ReportId = (int)report.Attribute("id"),
ReportName = (string)report.Attribute("name"),
DepartmentId = (int)department.Attribute("id"),
DepartmentName = (string)department.Attribute("name"),
ItemId = (string)item.Attribute("id"),
ItemName = (string)item.Attribute("name"),
DetailId = (string)detail.Attribute("id"),
DetailValue = (int)detail.Attribute("value"),
};
If you want it as a DataTable, you can use the following extension method:
public static DataTable ToDataTable<T>(this IEnumerable<T> source)
{
PropertyInfo[] properties = typeof(T).GetProperties()
.Where(p => p.CanRead && !p.GetIndexParameters().Any())
.ToArray();
DataTable table = new DataTable();
foreach (var p in properties)
{
Type type = p.PropertyType;
bool allowNull = !type.IsValueType;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
allowNull = true;
type = Nullable.GetUnderlyingType(type);
}
DataColumn column = table.Columns.Add(p.Name, type);
column.AllowDBNull = allowNull;
column.ReadOnly = !p.CanWrite;
}
foreach (var item in source)
{
DataRow row = table.NewRow();
foreach (var p in properties)
{
object value = p.GetValue(item, null) ?? DBNull.Value;
row[p.Name] = value;
}
table.Rows.Add(row);
}
return table;
}
Use it like this:
var table = details.CopyToDataTable();

Related

Indirection with linq : Change query expression "Select" according users parameters

I use a table "TLanguage" to record lable results of my site. I have 4 columns in this table: French, English, German and Spanish.
In a MVC application I use this query:
var req = (from TYP in context.TYP_TypeMission
join ML in context.TLanguage on TYP.IDTMultiLanguage equals ML.IDTMultiLanguage
where TYP.IDTFiliale == idFiliale
orderby TYP.LibTypeMission
select new SelectListItem
{
Selected = TYP.IdTypeMission == idTypeMission,
Value = SqlFunctions.StringConvert((double)TYP.IdTypeMission),
Text = ML.French
}).ToList();
How can I change ML.French by ML.English or ML.German in my query according the language of my site?
Is it possible to create an indirection in the query?
It is possible to parametrize the mapping like this:
public class TLanguageMap : EntityTypeConfiguration<TLanguage>
{
public TLanguageMap(string language)
{
this.HasKey(t => t.TLanguageId);
this.Property(t => t.Translation).HasColumnName(language);
}
}
In the context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TLanguageMap(this._language));
}
and its constructor:
public LocalizableContext(string language)
{
this._language = language;
}
Now when constructing a context you can determine for which language it is:
var context = new LocalizableContext("French");
And the query will always be:
...
select new SelectListItem
{
Selected = TYP.IdTypeMission == idTypeMission,
Value = SqlFunctions.StringConvert((double)TYP.IdTypeMission),
Text = ML.Translation
})
You may want to make it more robust by using an enum for the languages and a switch statement to get the database column names.
You can save the beginning of your query into variable and change only the last part later:
var query = from TYP in context.TYP_TypeMission
join ML in context.TLanguage on TYP.IDTMultiLanguage equals ML.IDTMultiLanguage
where TYP.IDTFiliale == idFiliale
orderby TYP.LibTypeMission
select new { ML, TYP };
List<SelectListItem> req;
if(Site.Lang == "DE")
{
req = (from item in query
select new SelectListItem
{
Selected = item.TYP.IdTypeMission == idTypeMission,
Value = SqlFunctions.StringConvert((double)item.TYP.IdTypeMission),
Text = item.ML.German
}).ToList();
}
else if(Site.Lang == "FR")
{
req = (from item in query
select new SelectListItem
{
Selected = item.TYP.IdTypeMission == idTypeMission,
Value = SqlFunctions.StringConvert((double)item.TYP.IdTypeMission),
Text = item.ML.French
}).ToList();
}
else
{
req = (from item in query
select new SelectListItem
{
Selected = item.TYP.IdTypeMission == idTypeMission,
Value = SqlFunctions.StringConvert((double)item.TYP.IdTypeMission),
Text = item.ML.English
}).ToList();
}
Query won't be executed until ToList() is called.

Dynamically Sorting with LINQ

I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.
I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.
Currently, I'm trying the following without any luck:
var results = myCollection.OrderBy(sortProperty);
However, I'm getting a message that says:
... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.
Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)
I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:
// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.Name == "OrderBy"
&& method.GetParameters().Length == 2)
.Single();
public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
this IEnumerable<TSource> source,
string propertyName)
{
// TODO: Lots of validation :)
PropertyInfo property = typeof(TSource).GetProperty(propertyName);
MethodInfo getter = property.GetGetMethod();
Type propType = property.PropertyType;
Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
Delegate func = Delegate.CreateDelegate(funcType, getter);
MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
typeof(TSource), propType);
return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
new object[] { source, func });
}
Test code:
string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };
foreach (string x in foo.OrderByProperty("Length"))
{
Console.WriteLine(x);
}
Output:
Jon
Tom
Holly
Robin
William
It even returns an IOrderedEnumerable<TSource> so you can chain ThenBy clauses on as normal :)
You need to build an Expression Tree and pass it to OrderBy.
It would look something like this:
var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
Expression.Property(param, sortProperty),
param
);
Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.
protected void sort_grd(object sender, GridViewSortEventArgs e)
{
if (Convert.ToBoolean(ViewState["order"]) == true)
{
ViewState["order"] = false;
}
else
{
ViewState["order"] = true;
}
ViewState["SortExp"] = e.SortExpression;
dataBind(Convert.ToBoolean(ViewState["order"]), e.SortExpression);
}
public void dataBind(bool ord, string SortExp)
{
var db = new DataClasses1DataContext(); //linq to sql class
var Name = from Ban in db.tbl_Names.AsEnumerable()
select new
{
First_Name = Ban.Banner_Name,
Last_Name = Ban.Banner_Project
};
if (ord)
{
Name = BannerName.OrderBy(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
else
{
Name = BannerName.OrderByDescending(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
}
grdSelectColumn.DataSource = Name ;
grdSelectColumn.DataBind();
}
you can do this with Linq
var results = from c in myCollection
orderby c.SortProperty
select c;
For dynamic sorting you could evaluate the string i.e. something like
List<MyObject> foo = new List<MyObject>();
string sortProperty = "LastName";
var result = foo.OrderBy(x =>
{
if (sortProperty == "LastName")
return x.LastName;
else
return x.FirstName;
});
For a more generic solution see this SO thread: Strongly typed dynamic Linq sorting
For this sort of dynamic work I've been using the Dynamic LINQ library which makes this sort of thing easy:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx
You can copy paste the method I post in that answer, and change the signature/method names:
How to make the position of a LINQ Query SELECT variable
You can actually use your original line of code
var results = myCollection.OrderBy(sortProperty);
simply by using the System.Linq.Dynamic library.
If you get a compiler error (something like cannot convert from or does not contain a definition...) you may have to do it like this:
var results = myCollection.AsQueryable().OrderBy(sortProperty);
No need for any expression trees or data binding.
You will need to use reflection to get the PropertyInfo, and then use that to build an expression tree. Something like this:
var entityType = typeof(TEntity);
var prop = entityType.GetProperty(sortProperty);
var param = Expression.Parameter(entityType, "x");
var access = Expression.Lambda(Expression.MakeMemberAccess(param, prop), param);
var ordered = (IOrderedQueryable<TEntity>) Queryable.OrderBy(
myCollection,
(dynamic) access);

Iterating tables in a context and the properties of those tables

I'm iterating the tables of a context and then the properties of those tables to eager load all columns in a context. I received some help via another question, but I don't seem to be able to figure out how to iterate the column properties of the actual table.
Final working code:
public static void DisableLazyLoading(this DataContext context)
{
DataLoadOptions options = new DataLoadOptions();
var contextTables = context.GetType().GetProperties().Where(n => n.PropertyType.Name == "Table`1");
foreach (var contextTable in contextTables)
{
var tableType = contextTable.GetValue(context, null).GetType().GetGenericArguments()[0];
var tableProperties = tableType.GetProperties().Where(n => n.PropertyType.Name != "EntitySet`1");
foreach (var tableProperty in tableProperties)
{
ParameterExpression paramExp = Expression.Parameter(tableType, "s");
Expression expr = Expression.Property(paramExp, tableProperty.Name);
options.LoadWith(Expression.Lambda(expr, paramExp));
}
}
context.LoadOptions = options;
}
You're only getting the ProperInfos. You need to get the values from the PropertyInfos:
var tablePropertInfos = context.GetType().GetProperties().Where(
n => n.PropertyType.Name == "Table`1");
foreach (var tablePropertyInfo in tablePropertInfos)
{
// Get the actual table
var table = tablePropertyInfo.GetValue(context, null);
// Do the same for the actual table properties
}
Once you have the PropertyInfo class, you need to get the value using the GetValue method.

dataset to List<T>using linq

I have a DataSet and I want to convert the DataSet into List<T>
T - type object
How convert my DataSet? It has 10 columns, with all 10 properties my object has and it's returning over 15000 rows. I want to return that dataset into List<obj> and loop it how do I do that?
This is pretty much the same as the other answers, but introduces strongly-typed columns.
var myData = ds.Tables[0].AsEnumerable().Select(r => new {
column1 = r.Field<string>("column1"),
column2 = r.Field<int>("column2"),
column3 = r.Field<decimal?>("column3")
});
var list = myData.ToList(); // For if you really need a List and not IEnumerable
I think this should do it.
var output = yourDataSet.Tables[0].Rows.Cast<DataRow>().Select(r => new
{
Column1 = r["Column1"].ToString(),
Column2 = r["Column2"].ToString(),
Column3 = r["Column3"].ToString(),
Column4 = r["Column4"].ToString(),
Column5 = r["Column5"].ToString(),
Column6 = r["Column6"].ToString(),
Column7 = r["Column7"].ToString(),
Column8 = r["Column8"].ToString(),
Column9 = r["Column9"].ToString(),
Column10 = r["Column10"].ToString()
}).ToList();
First of all, you're on the right track, but you should be thinking in terms of IEnumerable<T> rather than List<T>. And here is how you would do that:
var myData = ds.Tables[0].AsEnumerable()
.Select(r => new {column1 = r[0].ToString(),
column2 = r[1].ToString()
/*etc*/
});
Never convert an IEnumerable to a List before you absolutely need to.
I know #bharat asked for a solution using LINQ, but mainly for myself I wanted to compare #Kelsey's solution to the old fashioned way of doing this:
List<Obj> list = new List<Obj>();
foreach (DataRow r in yourDataSet.Tables[0].Rows)
{
Obj obj = new Obj();
obj.Column1 = r["Column1"];
obj.Column2 = r["Column2"];
obj.Column3 = r["Column3"];
obj.Column4 = r["Column4"];
obj.Column5 = r["Column5"];
obj.Column6 = r["Column6"];
obj.Column7 = r["Column7"];
obj.Column8 = r["Column8"];
obj.Column9 = r["Column9"];
obj.Column10 = r["Column10"];
list.Add(obj);
}
Or via constructor:
List<Obj> list = new List<Obj>();
foreach (DataRow r in yourDataSet.Tables[0].Rows)
{
Obj obj = new Obj(r["Column1"], r["Column2"], r["Column3"], r["Column4"], r["Column5"],r["Column6"], r["Column7"], r["Column8"], r["Column9"],r["Column10"]);
list.Add(obj);
}
I intentionally left off .ToString() because I think using it depends on the situation.
Thanks for all the above posts...
I have done it with using Linq Query, for detail visit the link
http://codenicely.blogspot.com/2012/02/converting-your-datatable-into-list.html
A very simple approach that I use is following:
List<Obj> objList = new List<Obj>();
foreach (DataRow _dataRow in dataSet.Tables[0].Rows)
{
Obj obj = new Obj();
obj.Col1 = Convert.ToInt32(_dataRow["Col1"]);
obj.Col2 = Convert.ToInt32(_dataRow["Col2"]);
obj.Col3 = Convert.ToString(_dataRow["Col3"]);
objList.Add(obj);
}

How can I do an OrderBy with a dynamic string parameter?

I want to do this:
var orderBy = "Nome, Cognome desc";
var timb = time.Timbratures.Include("Anagrafica_Dipendente")
.Where(p => p.CodDipendente == 1);
if(orderBy != "")
timb = timb.OrderBy(orderBy);
Is there an OrderBy overload available that accepts a string parameter?
If you are using plain LINQ-to-objects and don't want to take a dependency on an external library it is not hard to achieve what you want.
The OrderBy() clause accepts a Func<TSource, TKey> that gets a sort key from a source element. You can define the function outside the OrderBy() clause:
Func<Item, Object> orderByFunc = null;
You can then assign it to different values depending on the sort criteria:
if (sortOrder == SortOrder.SortByName)
orderByFunc = item => item.Name;
else if (sortOrder == SortOrder.SortByRank)
orderByFunc = item => item.Rank;
Then you can sort:
var sortedItems = items.OrderBy(orderByFunc);
This example assumes that the source type is Item that have properties Name and Rank.
Note that in this example TKey is Object to not constrain the property types that can be sorted on. If the func returns a value type (like Int32) it will get boxed when sorting and that is somewhat inefficient. If you can constrain TKey to a specific value type you can work around this problem.
Absolutely. You can use the LINQ Dynamic Query Library, found on Scott Guthrie's blog. There's also an updated version available on CodePlex.
It lets you create OrderBy clauses, Where clauses, and just about everything else by passing in string parameters. It works great for creating generic code for sorting/filtering grids, etc.
var result = data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy("Foo asc");
var query = DbContext.Data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy("Foo ascending");
Another solution from codeConcussion (https://stackoverflow.com/a/7265394/2793768)
var param = "Address";
var pi = typeof(Student).GetProperty(param);
var orderByAddress = items.OrderBy(x => pi.GetValue(x, null));
The simplest & the best solution:
mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s));
You don't need an external library for this. The below code works for LINQ to SQL/entities.
/// <summary>
/// Sorts the elements of a sequence according to a key and the sort order.
/// </summary>
/// <typeparam name="TSource">The type of the elements of <paramref name="query" />.</typeparam>
/// <param name="query">A sequence of values to order.</param>
/// <param name="key">Name of the property of <see cref="TSource"/> by which to sort the elements.</param>
/// <param name="ascending">True for ascending order, false for descending order.</param>
/// <returns>An <see cref="T:System.Linq.IOrderedQueryable`1" /> whose elements are sorted according to a key and sort order.</returns>
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> query, string key, bool ascending = true)
{
if (string.IsNullOrWhiteSpace(key))
{
return query;
}
var lambda = (dynamic)CreateExpression(typeof(TSource), key);
return ascending
? Queryable.OrderBy(query, lambda)
: Queryable.OrderByDescending(query, lambda);
}
private static LambdaExpression CreateExpression(Type type, string propertyName)
{
var param = Expression.Parameter(type, "x");
Expression body = param;
foreach (var member in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
return Expression.Lambda(body, param);
}
(CreateExpression copied from https://stackoverflow.com/a/16208620/111438)
I did so:
using System.Linq.Expressions;
namespace System.Linq
{
public static class LinqExtensions
{
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string field, string dir = "asc")
{
// parametro => expressão
var parametro = Expression.Parameter(typeof(TSource), "r");
var expressao = Expression.Property(parametro, field);
var lambda = Expression.Lambda(expressao, parametro); // r => r.AlgumaCoisa
var tipo = typeof(TSource).GetProperty(field).PropertyType;
var nome = "OrderBy";
if (string.Equals(dir, "desc", StringComparison.InvariantCultureIgnoreCase))
{
nome = "OrderByDescending";
}
var metodo = typeof(Queryable).GetMethods().First(m => m.Name == nome && m.GetParameters().Length == 2);
var metodoGenerico = metodo.MakeGenericMethod(new[] { typeof(TSource), tipo });
return metodoGenerico.Invoke(source, new object[] { source, lambda }) as IOrderedQueryable<TSource>;
}
public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, string field, string dir = "asc")
{
var parametro = Expression.Parameter(typeof(TSource), "r");
var expressao = Expression.Property(parametro, field);
var lambda = Expression.Lambda<Func<TSource, string>>(expressao, parametro); // r => r.AlgumaCoisa
var tipo = typeof(TSource).GetProperty(field).PropertyType;
var nome = "ThenBy";
if (string.Equals(dir, "desc", StringComparison.InvariantCultureIgnoreCase))
{
nome = "ThenByDescending";
}
var metodo = typeof(Queryable).GetMethods().First(m => m.Name == nome && m.GetParameters().Length == 2);
var metodoGenerico = metodo.MakeGenericMethod(new[] { typeof(TSource), tipo });
return metodoGenerico.Invoke(source, new object[] { source, lambda }) as IOrderedQueryable<TSource>;
}
}
}
Use :
example.OrderBy("Nome", "desc").ThenBy("other")
Work like:
example.OrderByDescending(r => r.Nome).ThenBy(r => r.other)
Look at this blog here. It describes a way to do this, by defining an EntitySorter<T>.
It allows you to pass in an IEntitySorter<T> into your service methods and use it like this:
public static Person[] GetAllPersons(IEntitySorter<Person> sorter)
{
using (var db = ContextFactory.CreateContext())
{
IOrderedQueryable<Person> sortedList = sorter.Sort(db.Persons);
return sortedList.ToArray();
}
}
And you can create an EntitiySorter like this:
IEntitySorter<Person> sorter = EntitySorter<Person>
.OrderBy(p => p.Name)
.ThenByDescending(p => p.Id);
Or like this:
var sorter = EntitySorter<Person>
.OrderByDescending("Address.City")
.ThenBy("Id");
You need to use the LINQ Dynamic Query Library in order to pass parameters at runtime,
This will allow linq statements like
string orderedBy = "Description";
var query = (from p in products
orderby(orderedBy)
select p);
If your columnName is in a variable col, then
string col="name";
list.OrderBy(x=>x[col])
As what Martin Liversage said, you can define a Func<>before you pass it to OrderBy method, but I found an interesting way to do that.
You can define a dictionary from string to Func<> like this :
Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
{"Rank", x => x.Rank}
};
And use it like this :
yourList.OrderBy(SortParameters["Rank"]);
In this case you can dynamically sort by string.
In one answer above:
The simplest & the best solution:
mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s));
There is an syntax error, ,null must be added:
mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s,null));

Resources