- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
EntityCollection to DataTable when entity has custom fields
Joined: 23-Jan-2005
I am looking for a good way to create property projectors for an entity with custom fields.
For a very simplified example:
public partial class CustomerEntity
{
public string CityAndCountry
{
get
{
return String.Format("{0}, {1}", this.City, this.Country);
}
}
}
This code will get me a datatable with the fields from the generated entity but it will not include the fields from the extended partial class. The CityAndCountry field will not be included in the datatable.
EntityCollection<CustomerEntity> customers;
// skip code to fill Entity collection
string tableName = "Customers"
List<IEntityPropertyProjector> propertyProjectors =
EntityFields2.ConvertToProjectors(customers.EntityFactoryToUse.CreateFields());
DataTable table = new DataTable(tableName);
collection.DefaultView.CreateProjection(propertyProjectors, table);
Right now I am looping through the properties using reflection to find the additional properties and add them to the PropertyProjector list. That doesn't seem very efficient to me. Is there a better way?
Hi Joel,
LLBLGen generated doesn't know about your custom properties, you you have to add them manually (either explicitly or by reflection). If you want to automate that you could write your own projector, but at the end you have to use similar code inside.
Joined: 23-Jan-2005
I know that I can create the property projectors in code. I'm looking for a shortcut to doing that. It seems like this functionality should be (or already is) built-in somewhere.
It would probably be good for me to be more clear on my goals. Entities and Entity collections have been great for most of my work. For one of my current projects, I will need to get DataTables to use with a reporting tool. I want those DataTables to include the custom fields in the EntityCollections that I already have. I'd also like to use the "new" QuerySpec syntax for some things.
At this point, I've created an extension method to do what I need done with the EntityCollections. However, this limits me to entity collections and I don't see how I can use it with QuerySpec.
public static DataTable ToDataTable(this IEntityCollection2 entityCollection, string tableName)
{
IEntity2 entity = entityCollection.EntityFactoryToUse.Create();
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
List<String> propsToIgnore = new List<String>();
//add entity fields
foreach (IEntityField2 field in entity.Fields)
{
propertyProjectors.Add(new EntityPropertyProjector(field, field.Name));
propsToIgnore.Add(field.Name);
}
var coreProperties = new List<string> {"ActiveContext", "AuditorToUse", "AuthorizerToUse",
"ConcurrencyPredicateFactoryToUse", "CustomProperties", "Fields", "FieldsCustomProperties",
"IsNew", "IsDirty", "Relations", "Validator"};
propsToIgnore.AddRange(coreProperties);
// add custom property fields
foreach (PropertyInfo p in entity.GetType().GetProperties())
{
if (p.CanRead)
{
if (p.PropertyType == typeof(IPrefetchPathElement2))
{
//ignore prefetch properties
}
else if (typeof(IEntityCollection2).IsAssignableFrom(p.PropertyType))
{
//ignore entity collection properties for related entities
}
else
{
if (!propsToIgnore.Contains(p.Name))
{
EntityProperty entityProp = new EntityProperty(p.Name);
propertyProjectors.Add(new EntityPropertyProjector(entityProp, p.Name));
}
}
}
}
DataTable table = new DataTable(tableName);
entityCollection.DefaultView.CreateProjection(propertyProjectors, table);
return table;
}
Given the partial class in my first post, the first query below will result in a DataTable with 11 columns and the second query (with the extension method) will result in a DataTable with 12 columns.
string country = "USA";
DataTable table;
var qf = new QueryFactory();
PredicateExpression filter = new PredicateExpression();
filter.Add(CustomerFields.Country == country);
DynamicQuery query = qf.Customer
.Select(Projection.Full)
.Where(CustomerFields.Country.Equal(country));
using (var adapter = new DataAccessAdapter())
{
table = adapter.FetchAsDataTable(query);
Console.WriteLine("\nRow count for {0}: {1}, Column count: {2}", country, table.Rows.Count, table.Columns.Count);
foreach (DataColumn dc in table.Columns)
{
Console.WriteLine("{0} - {1}", dc.ColumnName, dc.DataType);
}
}
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
RelationPredicateBucket bucket = new RelationPredicateBucket();
bucket.PredicateExpression.Add(filter);
//Console.WriteLine(query.ToString());
//ConsoleHelper.Pause();
using (var adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(customers, bucket);
table = customers.ToDataTable(customers.EntityFactoryToUse.ForEntityName);
Console.WriteLine("\nRow count for {0}: {1}, Column count: {2}", country, table.Rows.Count, table.Columns.Count);
foreach (DataColumn dc in table.Columns)
{
Console.WriteLine("{0} - {1}", dc.ColumnName, dc.DataType);
}
}
I'd like to know whether there is a better way to do this. Am I taking the wrong approach to the custom fields?
I see you need to dynamically grab the properties to project, and I can't think of a completely different alternative.
I'll just add couple of suggestions: 1- You might consider ignoring IEntity type properties as well (Order.Customer).
2- You might use Attributes to decorate the Custom Properties you need to project. Then in your projection code you will only project those properties with the defined attribute.
Joined: 23-Jan-2005
Thanks, those were both very helpful suggestions. I especially like the second one. For my benefit and anyone else who reads this thread, I'll show the code modifications.
If I adapt my previous code to your first comment, it looks like this:
public static DataTable ToDataTable(this IEntityCollection2 entityCollection, string tableName)
{
IEntity2 entity = entityCollection.EntityFactoryToUse.Create();
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
List<String> propsToIgnore = new List<String>();
//add entity fields
foreach (IEntityField2 field in entity.Fields)
{
propertyProjectors.Add(new EntityPropertyProjector(field, field.Name));
propsToIgnore.Add(field.Name);
}
var coreProperties = new List<string> {"ActiveContext", "AuditorToUse", "AuthorizerToUse",
"ConcurrencyPredicateFactoryToUse", "CustomProperties", "Fields", "FieldsCustomProperties",
"IsNew", "IsDirty", "Relations", "Validator"};
propsToIgnore.AddRange(coreProperties);
// add custom property fields
foreach (PropertyInfo p in entity.GetType().GetProperties())
{
if (p.CanRead)
{
if (p.PropertyType == typeof(IPrefetchPathElement2))
{
//Console.WriteLine("***Ignore IPrefetchPathElement2 property: {0} ***", p.Name);
}
else if (typeof(IEntity2).IsAssignableFrom(p.PropertyType))
{
//Console.WriteLine("***Ignore IEntity2 property: {0} ***", p.Name);
}
else if (typeof(IEntityCollection2).IsAssignableFrom(p.PropertyType))
{
//Console.WriteLine("***Ignore IEntityCollection2 property: {0} ***", p.Name);
}
else
{
if (!propsToIgnore.Contains(p.Name))
{
Console.WriteLine("Custom field property: {0}", p.Name);
EntityProperty entityProp = new EntityProperty(p.Name);
propertyProjectors.Add(new EntityPropertyProjector(entityProp, p.Name));
}
}
}
}
DataTable table = new DataTable(tableName);
entityCollection.DefaultView.CreateProjection(propertyProjectors, table);
return table;
}
Taking your second suggestion, I create an attribute class.
public class EntityCustomFieldAttribute : System.Attribute
{
//this is a flag attribute only and will use the default constructor
}
I assign the attribute to the custom field(s).
/// <summary>
/// Example of extending an entity with a calculated field
/// </summary>
public partial class CustomerEntity
{
[EntityCustomFieldAttribute()]
public string CityCountry
{
get
{
return String.Format("{0}, {1}", this.City, this.Country);
}
}
}
Now the extension method code is much cleaner.
public static DataTable ToDataTable(this IEntityCollection2 entityCollection, string tableName)
{
IEntityFieldsCore fields = entityCollection.EntityFactoryToUse.CreateFields();
List<IEntityPropertyProjector> propertyProjectors = EntityFields2.ConvertToProjectors(fields);
IEntity2 entity = entityCollection.EntityFactoryToUse.Create();
var props = entity.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(EntityCustomFieldAttribute)));
foreach (PropertyInfo p in props)
{
if (p.CanRead)
{
Console.WriteLine("Custom field property: {0}", p.Name);
EntityProperty entityProp = new EntityProperty(p.Name);
propertyProjectors.Add(new EntityPropertyProjector(entityProp, p.Name));
}
}
DataTable table = new DataTable(tableName);
entityCollection.DefaultView.CreateProjection(propertyProjectors, table);
return table;
}
Any ideas at all on how I could code to pick up the custom fields with QuerySpec?
Queryspec works on the DB only, so it requires a direct translation between a field used and a DB field (i.e. it only works with real entity fields). Your custom properties aren't usable in queries which run on the DB, as they don't translate back to DB fields.
What exactly do you have in mind with the custom properties and queryspec/query system?
Joined: 23-Jan-2005
I'm just looking to be able to use the FetchDataTable method and have my custom property fields included. I understand they can't be used in the Where clause but it seems as though they should be available in the resultset by default.
I am using the partial class/custom field property path because that's what the documentation recommends. I've been happy with that result using entity collections but I now need to get that data as a datatable as well.
QuerySpec is the shiny new thing and I'm trying to understand whether it can do all of the things I need it to do before I recommend it to my developer team.