Web Api user filtering response fields - c#

I am creating a new web API and would like to allow the user to specify what fields get returned to them in the URL.
My current thoughts are:
For a sample model like this:
public class Value
{
public string ValueId { get; set; }
public int Number { get; set; }
public ValueInternal Internal { get; set; }
}
public class ValueInternal
{
public int Number { get; set; }
public string Something { get; set; }
}
and a URL like this
http://example.com/api/values/?_fields=Number,Internal(Something)
would return this
[
{
"Number": 0,
"Internal": {
"Number": 0
}
}
]
I have come up with the below method of achieving this, but it has some flaws. I.e. it couldn't handle if Internal was an enumerable of ValueInternal or has no support for include all or include all except, or if T and TResult are different types. Does anyone have any suggestions on how I can improve this or if there already exists a way of doing it that I am missing.
public static Expression<Func<T, TResult>> CreateSelector<T, TResult>() where TResult : new()
{
var property = "Number,Internal(Something)";
return arg => Process<T, TResult>(arg, default(TResult), property);
}
private static TResult Process<T, TResult>(T arg, TResult output, string propertyList) where TResult : new()
{
if (output == null)
{
output = new TResult();
}
if (string.IsNullOrEmpty(propertyList))
{
return output;
}
var properties = Regex.Split(propertyList, #"(?<!,[^(]+\([^)]+),");
foreach (var property in properties)
{
var propertyName = property;
var propertyInternalsMatch = Regex.Match(property, #"\(.*(?<!,[^(]+\([^)]+)\)");
var internalPropertyList = propertyInternalsMatch.Value;
if (!string.IsNullOrEmpty(internalPropertyList))
{
propertyName = property.Replace(internalPropertyList, "");
internalPropertyList = internalPropertyList.Replace("(", "");
internalPropertyList = internalPropertyList.Replace(")", "");
}
var tProperty = arg.GetType().GetProperty(propertyName);
if(tProperty == null) continue;
var tResultProperty = output.GetType().GetProperty(propertyName);
if(tResultProperty == null) continue;
if (tProperty.PropertyType.IsPrimitive || tProperty.PropertyType.IsValueType || (tProperty.PropertyType == typeof(string)))
{
tResultProperty.SetValue(output, tProperty.GetValue(arg));
}
else
{
var propertyInstance = Activator.CreateInstance(tResultProperty.PropertyType);
tResultProperty.SetValue(output, Process(tProperty.GetValue(arg), propertyInstance, internalPropertyList));
}
}
return output;
}
After a bit more reading I think I want to do something like the answer to this question LINQ : Dynamic select but that still has the same flaws my solution had

If you use OData support on your ASP .NET Web API you can jus use $select, but if you don't want to use it or your underlying system can't be easy queried using Linq, you can use a custom contract resolver, but in this case you are just reducing the serialization size, not the internal data traffic.
public class FieldsSelectContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.GetIsSpecified = (t) =>
{
var fields = HttpContext.Current.Request["fields"];
if (fields != null)
{
return fields.IndexOf(member.Name, StringComparison.OrdinalIgnoreCase) > -1;
}
return true;
};
return property;
}
}
and in WebApiConfig.cs set the custom contract resolver:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new FieldsSelectContractResolver();

Related

Document Update Using Driver Ignoring _id

I'd like to be able to update a key/value pair within a Document in a Collection without regard to the _id. This SO response appears to provide a way to do what I want, but it requires a focus on the _id key.
Essentially, what can I do to get my code, as shown below, to work without adding the [BsonIgnoreExtraElements] attribute to the class?
Based on the code which I'll show below, I think I'm close, and I do have a possible workaround, using the attribute, which I'll also show. However, I'd like to do this without decorating the classes with any attributes if possible.
Why no _id? Personally, I find the _id field just gets in the way. It's not part of any of my classes, and MongoDB isn't relational, so I simply don't use it. I freely admit that I may be completely missing the intent and idea behind the use of the _id field, but I simply don't see a need for it in MongoDB. Nevertheless, if possible, I'd like to focus on working without a focus on the _id field.
With that said, I have methods for retrieving a single document or an entire collection along with inserting a single document or entire collection, generically, without regard to the _id field.
I found that the key to ignoring the _id in a "select" is to use a Projection. I'm not all that familiar with the driver, but here are the lines of code that do the magic:
public const string ELEMENT_ID = "_id";
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
For background info, here is a retrieval method, ignoring _id:
public T GetSingle<T>(string property, string value) where T : class, new()
{
T tObject = null;
try
{
if (MongoContext.MongoClient != null && MongoContext.MongoDatabase != null)
{
string className = typeof(T).ToString();
int lastPeriod = className.LastIndexOf('.');
int length = className.Length - lastPeriod;
className = className.Substring(lastPeriod + 1, length - 1);
if (!string.IsNullOrEmpty(className))
{
IMongoCollection<T> mongoCollection = MongoContext.MongoDatabase.GetCollection<T>(className);
if (mongoCollection != null)
{
BsonDocument bsonDocument = new BsonDocument();
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
PropertyInfo[] propertyInfo = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.Length > 0)
{
IEnumerable<PropertyInfo> piExisting = propertyInfo.Where(pi => pi.Name.Equals(property, StringComparison.CurrentCultureIgnoreCase));
if (piExisting != null && piExisting.Any())
{
BsonValue bsonValue = BsonValue.Create(value);
BsonElement bsonElement = new BsonElement(property, bsonValue);
if (bsonElement != null)
{
bsonDocument.Add(bsonElement);
}
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
if (found != null)
{
tObject = found.FirstOrDefault<T>();
}
}
}
}
}
}
}
catch (Exception ex)
{
Logger.WriteToLog(Logger.LoggerMessage(ex));
}
return tObject;
}
Similar to other another SO response, I've tried to use FindOneAndUpdate, but I receive the following error:
Element '_id' does not match any field or property of class
ClassThingy.Suffix.
If I could apply the Projection to the FindOneAndUpdate somehow, I think that might resolve my issue, but I'm not able to find a way to do that application.
Here is my code:
public T UpdateSingle<T>(T item, string property, object originalValue, object newValue) where T : class, new()
{
string className = string.Empty;
T updatedDocument = null;
try
{
if (MongoContext.MongoClient != null && MongoContext.MongoDatabase != null)
{
className = ClassUtility.GetClassNameFromObject<T>(item);
if (!string.IsNullOrEmpty(className))
{
IMongoCollection<T> mongoCollection = MongoContext.MongoDatabase.GetCollection<T>(className);
if (mongoCollection != null)
{
BsonDocument bsonDocument = new BsonDocument();
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
PropertyInfo[] propertyInfo = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.Length > 0)
{
IEnumerable<PropertyInfo> piExisting = propertyInfo.Where(pi => pi.Name.Equals(property, StringComparison.CurrentCultureIgnoreCase));
if (piExisting != null && piExisting.Any())
{
BsonValue bsonValue = BsonValue.Create(originalValue);
BsonElement bsonElement = new BsonElement(property, bsonValue);
if (bsonElement != null)
{
bsonDocument.Add(bsonElement);
}
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
if (found != null)
{
FilterDefinition<T> filterDefinition = Builders<T>.Filter.Eq(property, originalValue);
UpdateDefinition<T> updateDefinition = Builders<T>.Update.Set(property, newValue);
updatedDocument = mongoCollection.FindOneAndUpdate<T>(filterDefinition, updateDefinition);
}
}
}
}
}
}
}
catch (Exception ex)
{
Logger.WriteToLog(Logger.LoggerMessage(ex));
}
return updatedDocument;
}
Interestingly enough, while the FindOneAndUpdate method actually appears to succeed and the "Suffix" collection does, in fact, get modified. Also, the return doesn't contain the modification. Instead, it's the "original" (when I use the workaround as shown below).
More Info:
Suffix Class:
public class Suffix
{
public string Code { get; set; }
public string Description { get; set; }
}
Suffix suffix = new Suffix();
MongoRepository.MongoRepository mongoRepository = new MongoRepository.MongoRepository("MyDataBase");
mongoRepository.UpdateSingle<Suffix>(suffix, "Description", "Jr", "Junior");
Workaround:
[BsonIgnoreExtraElements]
public class Suffix
{
public string Code { get; set; }
public string Description { get; set; }
}
But, much rather not use the attribute if at all possible.
One thing you're missing here is the third parameter of .FindOneAndUpdate() method which is of type FindOneAndUpdateOptions<T,T>. That's the place where you can define if you want to get document After or Before the modification. Before is the default value Moreover you can specify the projection and exclude _id property. Try:
FilterDefinition<T> filterDefinition = Builders<T>.Filter.Eq(property, originalValue);
UpdateDefinition<T> updateDefinition = Builders<T>.Update.Set(property, newValue);
ProjectionDefinition<T, T> projection = Builders<T>.Projection.Exclude("_id");
var options = new FindOneAndUpdateOptions<T>()
{
Projection = projection,
ReturnDocument = ReturnDocument.After
};
updatedDocument = mongoCollection.FindOneAndUpdate<T>(filterDefinition, updateDefinition, options);

Validate response to filter array objects which are having no elements

How can I filter out the array objects which are having 0 elements from the ASP.NET Web API Model.
Ex: I'm am using the below method to filter null objects.
using Newtonsoft.Json;
public string FaxWork { get; set; }
[JsonProperty(PropertyName = "phoneWork", NullValueHandling = NullValueHandling.Ignore)]
How can I use something like above in order to filter out [] empty array objects?
Ex:
"postalAddress": [],
"electronicAddress": []
You can accomplish this using the conditional property serialization functionality of Json.NET.
If you just want to ignore a single member when its array value is empty, add a ShouldSerialize{PropertyName}() method to your class that returns false when you don't want it serialized, e.g.:
public class RootObject
{
public string[] PostalAddress { get; set; }
public bool ShouldSerializePostalAddress() { return PostalAddress != null && PostalAddress.Length > 0; }
}
If you need to do this for many different collection-valued members of many different types, you can create a custom contract resolver that automatically generates a ShouldSerialize predicate for all of then:
public class SkipEmptyCollectionsContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization)
.AddShouldSerializeEmptyCollections(this);
return property;
}
}
public static class JsonPropertyExtensions
{
public static JsonProperty AddShouldSerializeEmptyCollections(this JsonProperty property, IContractResolver resolver)
{
if (property == null)
throw new ArgumentNullException();
if ((typeof(IEnumerable).IsAssignableFrom(property.PropertyType) || property.PropertyType.IsAssignableFrom(typeof(IEnumerable)))
&& property.PropertyType != typeof(string)
&& property.Readable)
{
Predicate<object> shouldSerialize = (parent) =>
{
var value = property.ValueProvider.GetValue(parent);
if (value == null || value is string)
return true; // null properties are filtered by the NullValueHandling setting.
var contract = resolver.ResolveContract(value.GetType());
if (contract is JsonArrayContract)
{
return (value as IEnumerable).Any();
}
return true;
};
var oldShouldSerialize = property.ShouldSerialize;
if (oldShouldSerialize == null)
property.ShouldSerialize = shouldSerialize;
else
property.ShouldSerialize = (o) => shouldSerialize(o) && oldShouldSerialize(o);
}
return property;
}
}
public static class EnumerableExtensions
{
public static bool Any(this IEnumerable enumerable)
{
if (enumerable == null)
return false;
if (enumerable is ICollection)
{
return ((ICollection)enumerable).Count > 0;
}
var enumerator = enumerable.GetEnumerator();
using (enumerator as IDisposable)
{
return enumerator.MoveNext();
}
}
}
Then serialize using JsonSerializerSettings such as the following, which also enables camel casing of names:
var settings = new JsonSerializerSettings
{
ContractResolver = new SkipEmptyCollectionsContractResolver { NamingStrategy = new CamelCaseNamingStrategy() },
NullValueHandling = NullValueHandling.Ignore,
};
If you want to conditionally filter out empty collections using attributes, you could do so with the following contract resolver and attribute:
public enum EmptyArrayHandling
{
Include = 0,
Ignore = 1,
}
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class JsonPropertyExtensionsAttribute : System.Attribute
{
public EmptyArrayHandling EmptyArrayHandling { get; set; }
}
public class ConditionallySkipEmptyCollectionsContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var attr = property.AttributeProvider.GetAttributes(typeof(JsonPropertyExtensionsAttribute), false).Cast<JsonPropertyExtensionsAttribute>().FirstOrDefault();
if (attr != null && attr.EmptyArrayHandling == EmptyArrayHandling.Ignore)
property = property.AddShouldSerializeEmptyCollections(this);
return property;
}
}
Then apply to your members as follows:
public class RootObject
{
[JsonPropertyExtensions(EmptyArrayHandling = EmptyArrayHandling.Ignore)]
public string[] PostalAddress { get; set; }
}
Note that if your "collection" is actually a complex LINQ query, the ShouldSerialize method will have to enumerate the first element of the query to see if it is empty, which may lead to poor performance because the query will get evaluated twice. To avoid this, you can evaluate the entire query as a list before serializing.
You may want to cache the contract resolver for best performance.

How to add a property to a json object on the fly?

We are using Json.Net in our project to serialize and deserialize json objects.
Our entities have some DateTime properties and I would like to be able to convert them into PersianCalender DateTime and to provide them as string in my json object:
for example we have this entity :
public class PersonCertificate
{
public DateTime CertificateDate{get;set;}
}
I would like to have a json object like this :
{
"PersianCertificateDate":"1395/10/10"
}
So I thought that would be great to have an attribute named "AsPersianDate" for example so that I could do something like this:
public class PersonCertificate
{
[JsonIgnore]
[AsPersianDate]
public DateTime CertificateDate{get;set;}
}
I know that I can have a custom contract resolver to intercept json property creation process but I don't know how should I tell Json.Net to deserialize PersianCertificateDate into CertificateDate ?
OK it was far more easier than I thought.Actually ContractResolver is responsible for getting and setting all property values so here's what I have done:
public class EntityContractResolver:DefaultContractResolver
{
private class PersianDateValueProvider:IValueProvider
{
private readonly PropertyInfo _propertyInfo;
public PersianDateValueProvider(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
}
public void SetValue(object target, object value)
{
try
{
var date = value as string;
if(value==null && _propertyInfo.PropertyType==typeof(DateTime))
throw new InvalidDataException();
_propertyInfo.SetValue(target,date.ToGregorianDate());
}
catch (InvalidDataException)
{
throw new ValidationException(new[]
{
new ValidationError
{
ErrorMessage = "Date is not valid",
FieldName = _propertyInfo.Name,
TypeName = _propertyInfo.DeclaringType.FullName
}
});
}
}
public object GetValue(object target)
{
if(_propertyInfo.PropertyType.IsNullable() && _propertyInfo.GetValue(target)==null) return null;
try
{
return ((DateTime) _propertyInfo.GetValue(target)).ToPersian();
}
catch
{
return string.Empty;
}
}
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var list= base.CreateProperties(type, memberSerialization).ToList();
list.AddRange(type.GetProperties()
.Where(pInfo => IsAttributeDefined(pInfo,typeof(AsPersianDateAttribute))&& (pInfo.PropertyType == typeof (DateTime) || pInfo.PropertyType == typeof (DateTime?)))
.Select(CreatePersianDateTimeProperty));
return list;
}
private JsonProperty CreatePersianDateTimeProperty(PropertyInfo propertyInfo)
{
return new JsonProperty
{
PropertyName = "Persian"+propertyInfo.Name ,
PropertyType = typeof (string),
ValueProvider = new PersianDateValueProvider(propertyInfo),
Readable = true,
Writable = true
};
}
private bool IsAttributeDefined(PropertyInfo propertyInfo,Type attribute)
{
var metaDataAttribute = propertyInfo.DeclaringType.GetCustomAttribute<MetadataTypeAttribute>(true);
var metaDataProperty = metaDataAttribute?.MetadataClassType?.GetProperty(propertyInfo.Name);
var metaDataHasAttribute = metaDataProperty != null && Attribute.IsDefined(metaDataProperty, attribute);
return metaDataHasAttribute || Attribute.IsDefined(propertyInfo, attribute);
}
}

How do you use CsvHelper to write a class derived from DynamicObject?

I was hoping to use a dynamically typed object to write to a CSV file.
I'm receiving a 'CsvHelper.CsvWriterException' within the CsvWriter.WriteObject method with this message: "No properties are mapped for type 'WpmExport.DynamicEntry'."
Here is the class that I'm trying use :
public class DynamicEntry : DynamicObject
{
private Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
dictionary[binder.Name.ToLower()] = value;
return true;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys.AsEnumerable();
}
}
Anyone with any ideas or working examples? The documentation at http://joshclose.github.io/CsvHelper/ hints that it is possible but doesn't provide any guidance.
TIA
The functionality does not exist yet. You can write dynamic but not DynamicObject. You can view a thread on the subject here. https://github.com/JoshClose/CsvHelper/issues/187
When the functionality get implemented, I'll update the answer with the version it's in.
Update
This functionality will be available in 3.0. You can currently try out the 3.0-beta from NuGet.
Because I cannot wait for Version 3.0 (and CsvHelper.Excel to support it), I have found a interim-solution.
Got the class to export:
public partial class EntryReportInventory
{
public Guid DeviceId { get; set; }
[ReportProperty]
public string DeviceName { get; set; }
public Dictionary<string, object> InventoryValues { get; set; }
public EntryReportInventory(Device device, Dictionary<string, object> inventoryValues)
{
this.DeviceId = device.Id;
this.DeviceName = device.Name;
this.InventoryValues = inventoryValues;
}
}
Created Mapper:
Type genericClass = typeof(DefaultCsvClassMap<>);
Type constructedClass = genericClass.MakeGenericType(typeof(EntryReportInventory));
return (CsvClassMap)Activator.CreateInstance(constructedClass);
And now the magic. I iterate all properties.
foreach (PropertyInfo property in mapping)
{
...
if (isInventoryReportBaseType && typeof(Dictionary<string, object>).IsAssignableFrom(property.PropertyType))
{
var dataSource = (ReportInventoryBase)Activator.CreateInstance(entityType, dbContext);
foreach (var item in dataSource.ColumnNameAndText)
{
var columnName = item.Key;
var newMap = new CsvPropertyMap(property);
newMap.Name(columnName);
newMap.TypeConverter(new InventoryEntryListSpecifiedTypeConverter(item.Key));
customMap.PropertyMaps.Add(newMap);
}
...
}
And my converter is:
public class InventoryEntryListSpecifiedTypeConverter : CsvHelper.TypeConversion.ITypeConverter
{
private string indexKey;
public InventoryEntryListSpecifiedTypeConverter(string indexKey)
{
this.indexKey = indexKey;
}
public bool CanConvertFrom(Type type)
{
return true;
}
public bool CanConvertTo(Type type)
{
return true;
}
public object ConvertFromString(TypeConverterOptions options, string text)
{
throw new NotImplementedException();
}
public string ConvertToString(TypeConverterOptions options, object value)
{
var myValue = value as Dictionary<string, object>;
if (value == null || myValue.Count == 0) return null;
return myValue[indexKey] + "";
}
}
Don't know why, but it works to pass the same property several times.
That's it :)
You only have to have a list before (here: dataSource.ColumnNameAndText, filled from an external source) to identify the columns/values.

EF Query Object Pattern over Repository Example

I have built a repository which only exposes IEnumerable based mostly on the examples in "Professional ASP.NET Design Patterns" by Scott Millett.
However because he mostly uses NHibernate his example of how to implement the Query Object Pattern, or rather how to best translate the query into something useful in EF, is a bit lacking.
I am looking for a good example of an implementation of the Query Object Pattern using EF4.
Edit: The main issues with the trivial example in the book are that CreateQueryAndObjectParameters only handles 2 cases, Equal & LesserThanOrEqual - not exactly a complete query solution. And it uses a string to build the criterion - which is a very crude way to handle this when compared to NHibernate. He says he'll provide the EF code for the chapter 10 example, but its not in the download. Hence looking for a real world example.
You're probably sick of hearing from me at this point, but the natively supported IQueryable is the only Query Object Pattern implementation you need for EF.
According to the book ( "Professional ASP.NET Design Patterns" by Scott Millett) you can use this codes [I have improved some lines]:
Infrastructure layer :
Criterion class : (each Query can contain some Criterion)
public class Criterion
{
private string _propertyName;
private object _value;
private CriteriaOperator _criteriaOperator;
public Criterion(string propertyName, object value,
CriteriaOperator criteriaOperator)
{
_propertyName = propertyName;
_value = value;
_criteriaOperator = criteriaOperator;
}
public string PropertyName
{
get { return _propertyName; }
}
public object Value
{
get { return _value; }
}
public CriteriaOperator criteriaOperator
{
get { return _criteriaOperator; }
}
public static Criterion Create<T>(Expression<Func<T, object>> expression, object value, CriteriaOperator criteriaOperator)
{
string propertyName = PropertyNameHelper.ResolvePropertyName<T>(expression);
Criterion myCriterion = new Criterion(propertyName, value, criteriaOperator);
return myCriterion;
}
}
CriteriaOperator Class:
public enum CriteriaOperator
{
Equal,
LesserThanOrEqual,
NotApplicable
// TODO: Add the remainder of the criteria operators as required.
}
OrderByClause Class:
public class OrderByClause
{
public string PropertyName { get; set; }
public bool Desc { get; set; }
}
Query class:
public class Query
{
private QueryName _name;
private IList<Query> _subQueries = new List<Query>();
private IList<Criterion> _criteria = new List<Criterion>();
public Query()
: this(QueryName.Dynamic, new List<Criterion>())
{ }
public Query(QueryName name, IList<Criterion> criteria)
{
_name = name;
_criteria = criteria;
}
public QueryName Name
{
get { return _name; }
}
public bool IsNamedQuery()
{
return Name != QueryName.Dynamic;
}
public IEnumerable<Criterion> Criteria
{
get {return _criteria ;}
}
public void Add(Criterion criterion)
{
if (!IsNamedQuery())
_criteria.Add(criterion);
else
throw new ApplicationException("You cannot add additional criteria to named queries");
}
public IEnumerable<Query> SubQueries
{
get { return _subQueries; }
}
public void AddSubQuery(Query subQuery)
{
_subQueries.Add(subQuery);
}
public QueryOperator QueryOperator { get; set; }
public OrderByClause OrderByProperty { get; set; }
}
PropertyNameHelper class:
public static class PropertyNameHelper
{
public static string ResolvePropertyName<T>(
Expression<Func<T, object>> expression)
{
var expr = expression.Body as MemberExpression;
if (expr == null)
{
var u = expression.Body as UnaryExpression;
expr = u.Operand as MemberExpression;
}
return expr.ToString().Substring(expr.ToString().IndexOf(".") + 1);
}
}
You need a QueryTranslator class too, to translate query (in Repository layer):
public class TimelogQueryTranslator : QueryTranslator
{
public ObjectQuery<Timelog> Translate(Query query)
{
ObjectQuery<Timelog> timelogQuery;
if (query.IsNamedQuery())
{
timelogQuery = FindEFQueryFor(query);
}
else
{
StringBuilder queryBuilder = new StringBuilder();
IList<ObjectParameter> paraColl = new List<ObjectParameter>();
CreateQueryAndObjectParameters(query, queryBuilder, paraColl);
//[Edited By= Iman] :
if (query.OrderByProperty == null)
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray());
}
else if (query.OrderByProperty.Desc == true)
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray()).OrderBy(String.Format("it.{0} desc", query.OrderByProperty.PropertyName));
}
else
{
timelogQuery = DataContextFactory.GetDataContext().Timelogs
.Where(queryBuilder.ToString(), paraColl.ToArray()).OrderBy(String.Format("it.{0} asc", query.OrderByProperty.PropertyName));
}
//[Edited By= Iman] .
}
return timelogQuery;
}
//-------------------------------------------------------------
public abstract class QueryTranslator
{
public void CreateQueryAndObjectParameters(Query query, StringBuilder queryBuilder, IList<ObjectParameter> paraColl)
{
bool _isNotFirstFilterClause = false;
foreach (Criterion criterion in query.Criteria)
{
if (_isNotFirstFilterClause)
{
queryBuilder.Append(" AND "); //TODO: select depending on query.QueryOperator
}
switch (criterion.criteriaOperator)
{
case CriteriaOperator.Equal:
queryBuilder.Append(String.Format("it.{0} = #{0}", criterion.PropertyName));
break;
case CriteriaOperator.LesserThanOrEqual:
queryBuilder.Append(String.Format("it.{0} <= #{0}", criterion.PropertyName));
break;
default:
throw new ApplicationException("No operator defined");
}
paraColl.Add(new ObjectParameter(criterion.PropertyName, criterion.Value));
_isNotFirstFilterClause = true;
}
}
}
Now in your service layer:
public IEnumerable<Timelog> GetAllTimelogsFor(int iadcId, byte workShift)
{
Query query = new Query(QueryName.Dynamic,new List<Criterion>());
query.Add(Criterion.Create<Timelog>(t=>t.IadcId, iadcId, CriteriaOperator.Equal));
query.QueryOperator = QueryOperator.And;
query.Add(Criterion.Create<Timelog>(t=>t.Shift, workShift, CriteriaOperator.Equal));
query.OrderByProperty = new OrderByClause { PropertyName = "FromTime", Desc = false };
IEnumerable<Timelog> timelogs = _timelogRepository.FindBy(query);
return timelogs;
}

Categories

Resources