So we have a document database which stores our data as XML files. A new requirement is to create a graphQL interface to access the xmldocument.
I have implemented the graphQL interface with extensions:
https://github.com/graphql-dotnet/graphql-dotnet
https://github.com/graphql-dotnet/server
So far I can create the fields dynamically from the XML file:
public ReferenceType(IRepository repository)
{
var searchResult = repository.GetExampleForSchema();
foreach(var field in searchResult.Root.Elements())
{
if (!field.HasElements && field.Attribute(json + "Array") == null)
{
Field<StringGraphType>(field.Name.LocalName);
}
else
{
//TODO: Create child ObjectGraphTypes
}
}
}
But I can't find out how to return the result in my repository:
public dynamic Search()
{
var searchResult = new ExpandoObject() as IDictionary<string, object>;
//var searchResult = new { recordCount = 124345};
var xDocSearch = InternalSearch();
foreach (var field in xDocSearch.Root.Elements())
{
if (!field.HasElements && field.Attribute(json + "Array") == null)
{
var fieldName = field.Name.LocalName;
searchResult[fieldName] = field.Value;
}
else
{
//TODO: Create child objects
}
}
return searchResult;
}
The issue is, that GraphQL works with Reflection and uses this logic to read the field:
https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL/Resolvers/NameFieldResolver.cs
The ExpandoObject does not have my example recordCount as a property. When I create an anonymous type (see the commented line) it works.
So my questions are:
Is there a possibility to create a type with dynamic properties which can be accessed by reflections? (Can't influence how the value is read)
Is there maybe another way in the GraphQL Extension to solve this issue? Can I configure how the value is read?
Looking forward to some hints and tips!
Thanks,
Michael
So after some research and trying around I could solve it by myself:
It is possible to provide a resolve method for each field:
if (!field.HasElements && field.Attribute(json + "Array") == null)
{
Field<StringGraphType>(field.Name.LocalName, **resolve: ResolveXml**);
}
and in this method you can do what you want:
public object ResolveXml(ResolveFieldContext<object> context)
{
var source = (IDictionary<string, object>)context.Source;
return source[context.FieldName];
}
This is now only the first working solution, of course it might make more sense to return in the repository directly the XDocument and then resolve it here...
Maybe it helps someone as well!
Related
My Problem is this A have a API controler And creating a Single method delete to data from X DbSet properties but the don't have the same Generic paramether. My result is to somehow pass a System.Type to Generic paramether. And my Question is some way to do it?
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
I need to do something like (I know this can't work)
var table = TableInfo.GetValue(_context) as DbSet<TableInfo.GetType>;
My full code
[HttpDelete("{name}/{id}")]
[Route("api/Delete")]
public IActionResult Delete(string name = "Items", int id = 2)
{
PropertyInfo TableInfo = GetValueByName(name);
if (TableInfo == null)
return NotFound("Haaaah");
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
if (table == null)
return BadRequest();
var prop = table.SingleOrDefault(p => p.Id == id);
if (prop == null)
return NotFound(prop);
table.Remove(prop);
_context.SaveChanges();
return Ok();
}
public PropertyInfo GetValueByName(string name)
{
Type t = _context.GetType();
List<PropertyInfo> list = new List<PropertyInfo>(t.GetProperties());
foreach(PropertyInfo m in list)
{
if (m.Name == name)
return m;
}
return null;
}
For end sorry about my English.
And thanks for all answers :)
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
You can't do that, you have no compile time information on what type you need, how do you expect to leverage it before the code is even running?
If you really want compile time type information of table you either know the generic type at compile time or you cover all possible execution paths considering all potential generic types your method must handle (horrendous, don't do that).
Using an interface won't work either. A hypothetical IIdEntity and a cast along the lines table as DbSet<IIdEntity> will never work because:
Type variance is only allowed in interfaces and delegates, DbSet is not an interface.
Even if you use IDbSet<TEntity>, this interface is invariant in TEntity so the following will always fail:
class User: IIdEntity { ... }
object o = someDbEntityOfUser;
var db = o as IDbSet<IIdEntity> //will always be null.
The best options you have with your current setup are:
Keep using reflection; use it to inspect the Id property of the entities.
Use dynamic and simply let the runtime resolve the Id call.
I have an Outlook addin and am trying to avoid the world's largest switch. To get contact information, I am using items like: Microsoft.Office.Interop.Outlook.ContactItem.EmailAddress
Since there are many items, I would like to use a variable to select the item, like string myVariable="EmailAddress"; or string myVariable="FullName"; Then use ContactItem.myVariable. These items pulled from an xml file. I am drawing a blank and could use some help. Thank you.
I guess you could create an extension method that checks the properties, by using reflection.
Though keep in mind some of the remarks from your start post :)
using Outlook = Microsoft.Office.Interop.Outlook;
public static class ContactItemExtension
{
public static object Get(this Outlook.ContactItem item, string property)
{
object result = null;
if (item == null) {
// default null
return result;
}
var ctype = item.GetType();
var cprop = ctype.GetProperty(property);
if (cprop == null || cprop.GetGetMethod() == null) {
// no property found or it doesn't have a get
return result;
}
result = cprop.GetValue(item, null);
return result;
}
}
after which you can simple call, the Get method with the property name on top of any ContactItem
item.Get("EmailAddress1");
I've created a generic ObjectSet<T> in my generic repository.
What I would like to get is the name of the EntityKey of ObjectSet<T> so that I can use it in the DataContext.GetObjectByKey.
I've searched around and dug deep, but I can't seem to find this value anywhere in the ObjectSet class.
I looked a while ago for a nice way to do this and failed to find one. I generally end up building a GetEntityByKey extension method somewhere and within that, contatenating strings to build Entity Keys for TryGetObjectByKey calls. The general idea for building the entity key goes something like this:
internal class Program
{
private static void Main(string[] args)
{
var dc = new AdventureWorksLT2008Entities();
object c;
dc.TryGetObjectByKey(GetEntityKey(dc.Customers, 23), out c);
var customer = c as Customer;
Console.WriteLine(customer.EmailAddress);
}
private static EntityKey GetEntityKey<T>(ObjectSet<T> objectSet, object keyValue) where T : class
{
var entitySetName = objectSet.Context.DefaultContainerName + "." + objectSet.EntitySet.Name;
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] {new EntityKeyMember(keyPropertyName, keyValue)});
return entityKey;
}
}
You may be able to do something similar. This example assumes a single field per EntityKey for simplicity - for multiple value keys you would need to do something slightly more sophisticated with ObjectSet<T>.ElementType.KeyMembers and pass all your keys into the EntityKey constructor.
Generic:
public class GenericoRepositorio<T> : IGenericoRepositorio<T> where T : class
{
protected readonly ObjectSet<T> ObjetoSet;
protected readonly ModeloContainer Contexto;
public GenericoRepositorio(ModeloContainer contexto)
{
Contexto = contexto;
ObjetoSet = Contexto.CreateObjectSet<T>();
}
public T Carregar(int id)
{
object objeto;
Contexto.TryGetObjectByKey(GetEntityKey(ObjetoSet, id), out objeto);
return (T)objeto;
}
private static EntityKey GetEntityKey<T>(ObjectSet<T> objectSet, object keyValue) where T : class
{
var entitySetName = objectSet.Context.DefaultContainerName + "." + objectSet.EntitySet.Name;
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
return entityKey;
}
}
See this post that I made regarding getting the EntitySetName. For my repository, I create a property that gets the entity set name for the specific class name to do exactly what you are trying to do.
This should give you all the generic arguments (the types) for the ObjectSet:
objectSet.GetType().GetGenericArguments().First()
I had a tough time trying to do almost the same thing, getting the primary key name and value at runtime when the type is unknown. I was just get trying to implement an auditing scheme for deletes, and every solution i find involves superfluous code that I dont really understand. The EntityKey is not available from a DbContext, which is also confusing and annoying. The last 5 lines may save you 5 hours and 1 yr of baldness. I am not attempting this for Inserts, so if you do, you need to inspect those values carefully as they may be 0 or null.
foreach(var entry in ChangeTracker.Entries<IAuditable>())
{
...
case EntityState.Deleted:
var oc = ((IObjectContextAdapter)this).ObjectContext; //this is a DbContext
EntityKey ek = oc.ObjectStateManager.GetObjectStateEntry(entry.Entity).EntityKey;
var tablename = ek.EntitySetName;
var primaryKeyField = ek.EntityKeyValues[0].Key; //assumes only 1 primary key
var primaryKeyValue = ek.EntityKeyValues[0].Value;
var objContext = ((IObjectContextAdapter)this.context).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entityToUpdate);
Object foundEntity;
var exits = objContext.TryGetObjectByKey(entityKey, out foundEntity);
if (exits && this.dbset.Local != null && this.dbset.Local.Contains(foundEntity) &&this.dbset.Local.Any())
{
if (entityKey.EntityKeyValues != null && entityKey.EntityKeyValues.Any())
{
DbEntityEntry<T> entry = this.context.Entry(this.dbset.Find(entityKey.EntityKeyValues.FirstOrDefault().Value));
entry.CurrentValues.SetValues(entityToUpdate);
}
}
this.context.SaveChanges();
Tested with EF 6.
It will return an array of objects for each primary key value for the given DbEntityEntry.
Their maybe edge cases where this does not work - but for my simple needs works great.
Hope this helps someone else.
object[] GetPrimaryKeyValue(DbEntityEntry entry)
{
List<object> key = new List<object>();
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
if (objectStateEntry.EntityKey.EntityKeyValues != null && objectStateEntry.EntityKey.EntityKeyValues.Length==1)
{
key.Add(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
}
else
{
if (objectStateEntry.EntitySet.ElementType.KeyMembers.Any())
{
foreach (var keyMember in objectStateEntry.EntitySet.ElementType.KeyMembers)
{
if (entry.CurrentValues.PropertyNames.Contains(keyMember.Name))
{
var memberValue = entry.CurrentValues[keyMember.Name];
if (memberValue != null)
{
key.Add(memberValue);
}
}
}
}
}
return key.ToArray();
}
I like the approach of having property bag objects (DTOs) which define the interface to my server, but I don't like writing code like this:
void ModifyDataSomeWay(WibbleDTO wibbleDTO)
{
WibbleBOWithMethods wibbleBO = new WibbleBOWithMethods();
wibbleBO.Val1 = wibbleDTO.Val1;
wibbleBO.Val2 = wibbleDTO.Val2;
}
This copying code is quite laborious to write. If the copying code is unavoidable, then where do you put it? In the BO? In a factory? If it is possible to manually avoid writing the boiler plate code then how?
Thanks in advance.
That looks like a job for AutoMapper, or (simpler) just add some interfaces.
This needs more error handling, and you may need to modify it accommodate properties where the data types don't match, but this shows the essence of a simple solution.
public void CopyTo(object source, object destination)
{
var sourceProperties = source.GetType().GetProperties()
.Where(p => p.CanRead);
var destinationProperties = destination.GetType()
.GetProperties().Where(p => p.CanWrite);
foreach (var property in sourceProperties)
{
var targets = (from d in destinationProperties
where d.Name == property.Name
select d).ToList();
if (targets.Count == 0)
continue;
var activeProperty = targets[0];
object value = property.GetValue(source, null);
activeProperty.SetValue(destination, value, null);
}
}
Automapper (or similar tools) might be the way forward here. Another approach may be the factory pattern.
Simplest of all would be something like this:
class WibbleBO
{
public static WibbleBO FromData(WibbleDTO data)
{
return new WibbleBO
{
Val1 = data.Val1,
Val2 = data.Val2,
Val3 = ... // and so on..
};
}
}
I have created a function that takes a SQL command and produces output that can then be used to fill a List of class instances. The code works great. I've included a slightly simplified version without exception handling here just for reference - skip this code if you want to jump right the problem. If you have suggestions here, though, I'm all ears.
public List<T> ReturnList<T>() where T : new()
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
Type objectType = typeof (T);
FieldInfo[] typeFields = objectType.GetFields();
while (nwReader.Read())
{
T obj = new T();
foreach (FieldInfo info in typeFields)
{
for (int i = 0; i < nwReader.FieldCount; i++)
{
if (info.Name == nwReader.GetName(i))
{
info.SetValue(obj, nwReader[i]);
break;
}
}
}
fdList.Add(obj);
}
nwReader.Close();
return fdList;
}
As I say, this works just fine. However, I'd like to be able to call a similar function with an anonymous class for obvious reasons.
Question #1: it appears that I must construct an anonymous class instance in my call to my anonymous version of this function - is this right? An example call is:
.ReturnList(new { ClientID = 1, FirstName = "", LastName = "", Birthdate = DateTime.Today });
Question #2: the anonymous version of my ReturnList function is below. Can anyone tell me why the call to info.SetValue simply does nothing? It doesn't return an error or anything but neither does it change the value of the target field.
public List<T> ReturnList<T>(T sample)
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
// Cannot use FieldInfo[] on the type - it finds no fields.
var properties = TypeDescriptor.GetProperties(sample);
while (nwReader.Read())
{
// No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem?
T obj = (T)FormatterServices.GetUninitializedObject(typeof(T));
foreach (PropertyDescriptor info in properties)
{
for (int i = 0; i < nwReader.FieldCount; i++)
{
if (info.Name == nwReader.GetName(i))
{
// This loop runs fine but there is no change to obj!!
info.SetValue(obj, nwReader[i]);
break;
}
}
}
fdList.Add(obj);
}
nwReader.Close();
return fdList;
}
Any ideas?
Note: when I tried to use the FieldInfo array as I did in the function above, the typeFields array had zero elements (even though the objectType shows the field names - strange). Thus, I use TypeDescriptor.GetProperties instead.
Any other tips and guidance on the use of reflection or anonymous classes are appropriate here - I'm relatively new to this specific nook of the C# language.
UPDATE: I have to thank Jason for the key to solving this. Below is the revised code that will create a list of anonymous class instances, filling the fields of each instance from a query.
public List<T> ReturnList<T>(T sample)
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
var properties = TypeDescriptor.GetProperties(sample);
while (nwReader.Read())
{
int objIdx = 0;
object[] objArray = new object[properties.Count];
foreach (PropertyDescriptor info in properties)
objArray[objIdx++] = nwReader[info.Name];
fdList.Add((T)Activator.CreateInstance(sample.GetType(), objArray));
}
nwReader.Close();
return fdList;
}
Note that the query has been constructed and the parameters initialized in previous calls to this object's methods. The original code had an inner/outer loop combination so that the user could have fields in their anonymous class that didn't match a field. However, in order to simplify the design, I've decided not to permit this and have instead adopted the db field access recommended by Jason. Also, thanks to Dave Markle as well for helping me understand more about the tradeoffs in using Activator.CreateObject() versus GenUninitializedObject.
Anonymous types encapsulate a set of read-only properties. This explains
Why Type.GetFields returns an empty array when called on your anonymous type: anonymous types do not have public fields.
The public properties on an anonymous type are read-only and can not have their value set by a call to PropertyInfo.SetValue. If you call PropertyInfo.GetSetMethod on a property in an anonymous type, you will receive back null.
In fact, if you change
var properties = TypeDescriptor.GetProperties(sample);
while (nwReader.Read()) {
// No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem?
T obj = (T)FormatterServices.GetUninitializedObject(typeof(T));
foreach (PropertyDescriptor info in properties) {
for (int i = 0; i < nwReader.FieldCount; i++) {
if (info.Name == nwReader.GetName(i)) {
// This loop runs fine but there is no change to obj!!
info.SetValue(obj, nwReader[i]);
break;
}
}
}
fdList.Add(obj);
}
to
PropertyInfo[] properties = sample.GetType().GetProperties();
while (nwReader.Read()) {
// No way to create a constructor so this call creates the object without calling a ctor. Could this be a source of the problem?
T obj = (T)FormatterServices.GetUninitializedObject(typeof(T));
foreach (PropertyInfo info in properties) {
for (int i = 0; i < nwReader.FieldCount; i++) {
if (info.Name == nwReader.GetName(i)) {
// This loop will throw an exception as PropertyInfo.GetSetMethod fails
info.SetValue(obj, nwReader[i], null);
break;
}
}
}
fdList.Add(obj);
}
you will receive an exception informing you that the property set method can not be found.
Now, to solve your problem, what you can do is use Activator.CreateInstance. I'm sorry that I'm too lazy to type out the code for you, but the following will demonstrate how to use it.
var car = new { Make = "Honda", Model = "Civic", Year = 2008 };
var anothercar = Activator.CreateInstance(car.GetType(), new object[] { "Ford", "Focus", 2005 });
So just run through a loop, as you've done, to fill up the object array that you need to pass to Activator.CreateInstance and then call Activator.CreateInstance when the loop is done. Property order is important here as two anonymous types are the same if and only if they have the same number of properties with the same type and same name in the same order.
For more, see the MSDN page on anonymous types.
Lastly, and this is really an aside and not germane to your question, but the following code
foreach (PropertyDescriptor info in properties) {
for (int i = 0; i < nwReader.FieldCount; i++) {
if (info.Name == nwReader.GetName(i)) {
// This loop runs fine but there is no change to obj!!
info.SetValue(obj, nwReader[i]);
break;
}
}
}
could be simplified by
foreach (PropertyDescriptor info in properties) {
info.SetValue(obj, nwReader[info.Name]);
}
I had the same problem, I resolved it by creating a new Linq.Expression that's going to do the real job and compiling it into a lambda: here's my code for example:
I want to transform that call:
var customers = query.ToList(r => new
{
Id = r.Get<int>("Id"),
Name = r.Get<string>("Name"),
Age = r.Get<int>("Age"),
BirthDate = r.Get<DateTime?>("BirthDate"),
Bio = r.Get<string>("Bio"),
AccountBalance = r.Get<decimal?>("AccountBalance"),
});
to that call:
var customers = query.ToList(() => new
{
Id = default(int),
Name = default(string),
Age = default(int),
BirthDate = default(DateTime?),
Bio = default(string),
AccountBalance = default(decimal?)
});
and do the DataReader.Get things from the new method, the first method is:
public List<T> ToList<T>(FluentSelectQuery query, Func<IDataReader, T> mapper)
{
return ToList<T>(mapper, query.ToString(), query.Parameters);
}
I had to build an expression in the new method:
public List<T> ToList<T>(Expression<Func<T>> type, string sql, params object[] parameters)
{
var expression = (NewExpression)type.Body;
var constructor = expression.Constructor;
var members = expression.Members.ToList();
var dataReaderParam = Expression.Parameter(typeof(IDataReader));
var arguments = members.Select(member =>
{
var memberName = Expression.Constant(member.Name);
return Expression.Call(typeof(Utilities),
"Get",
new Type[] { ((PropertyInfo)member).PropertyType },
dataReaderParam, memberName);
}
).ToArray();
var body = Expression.New(constructor, arguments);
var mapper = Expression.Lambda<Func<IDataReader, T>>(body, dataReaderParam);
return ToList<T>(mapper.Compile(), sql, parameters);
}
Doing this that way, i can completely avoid the Activator.CreateInstance or the FormatterServices.GetUninitializedObject stuff, I bet it's a lot faster ;)
Question #2:
I don't really know, but I would tend to use Activator.CreateObject() instead of FormatterServices.GetUninitializedObject(), because your object might not be created properly. GetUninitializedObject() won't run a default constructor like CreateObject() will, and you don't necessarily know what's in the black box of T...
This method stores one line of a sql query in a variable of anonymous type. You have to pass a prototype to the method. If any property of the anonymous type can not be found within the sql query, it is filled with the prototype-value. C# creates constructors for its anonymous classes, the parameters have the same names as the (read-only) properties.
public static T GetValuesAs<T>(this SqlDataReader Reader, T prototype)
{
System.Reflection.ConstructorInfo constructor = prototype.GetType().GetConstructors()[0];
object[] paramValues = constructor.GetParameters().Select(
p => { try { return Reader[p.Name]; }
catch (Exception) { return prototype.GetType().GetProperty(p.Name).GetValue(prototype); } }
).ToArray();
return (T)prototype.GetType().GetConstructors()[0].Invoke(paramValues);
}