dynamic queries in dapper - c#

I am trying to work with this. I have written a unit test as a starter for ten like so:
[Fact]
public void TestOredGuids()
{
// Arrange
const string expectedSql = "SELECT * FROM Products WHERE SomeExternalForeignKey = #SomeExternalForeignKey OR Name = SomeExternalForeignKey = #SomeExternalForeignKey";
// Act
var result = DynamicQuery.GetDynamicQuery<Product>("Products", p => p.SomeExternalForeignKey == new Guid("28D3BCFB-9472-4141-BD88-BE5E7E1230F0") || p.SomeExternalForeignKey == new Guid("0F0DBA45-F842-4E46-9ED4-F50B5BCF0509"));
// Assert
}
internal class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime ExpiryDate { get; set; }
public int CategoryId { get; set; }
public Guid SomeExternalForeignKey { get; set; }
}
Unfortunately, I am always getting:
Additional information: 'System.Linq.Expressions.NewExpression' does not contain a definition for 'Value'
in the WalkTree method. Am I using GetDynamicQuery wrongly?
If there are any other implementation of dynamic sql mappers like this for dapper, I would appreciate any pointers. Thanks!

From what I see in the source code of the component you are trying to use, it expects the right operand of the expression to be a ConstantExpression (although for some unknown reason the author is using dynamic and expects a Value property), so to make it work, modify you code as follows
var someExternalForeignKey1 = new Guid("28D3BCFB-9472-4141-BD88-BE5E7E1230F0");
var someExternalForeignKey2 = new Guid("0F0DBA45-F842-4E46-9ED4-F50B5BCF0509");
var result = DynamicQuery.GetDynamicQuery<Product>("Products", p => p.SomeExternalForeignKey == someExternalForeignKey1 || p.SomeExternalForeignKey == someExternalForeignKey2);
Update: It turns out that the above also doesn't work, because of course it produces a closure which is not ConstantExpression. To make it work (as well as your original code), here are the required modifications of the DynamicQuery class
private static void WalkTree(BinaryExpression body, ExpressionType linkingType,
ref List<QueryParameter> queryProperties)
{
if (body.NodeType != ExpressionType.AndAlso && body.NodeType != ExpressionType.OrElse)
{
string propertyName = GetPropertyName(body);
var propertyValue = GetPropertyValue(body.Right);
string opr = GetOperator(body.NodeType);
string link = GetOperator(linkingType);
queryProperties.Add(new QueryParameter(link, propertyName, propertyValue, opr));
}
else
{
WalkTree((BinaryExpression)body.Left, body.NodeType, ref queryProperties);
WalkTree((BinaryExpression)body.Right, body.NodeType, ref queryProperties);
}
}
private static object GetPropertyValue(Expression source)
{
var constantExpression = source as ConstantExpression;
if (constantExpression != null)
return constantExpression.Value;
var evalExpr = Expression.Lambda<Func<object>>(Expression.Convert(source, typeof(object)));
var evalFunc = evalExpr.Compile();
var value = evalFunc();
return value;
}
But note that the whole class (as the author states) is just an example, and for instance maps just one parameter (thus one value) per property, so in order to make it really useful, the GetDynamicQuery method needs additional work. You might try this one instead. Hope that helps.

Related

How to reference property of another object if the current object's property is null

I have a public Dictionary<string, PostRenewalActionJobs> Jobs to store some actions I would like to trigger for specific accounts, the key of this dictionary being the account name.
public class PostRenewalActionJobs
{
public List<AlterDatabaseLinkJob> AlterDataBaseLink { get; set; }
public DatabaseConnectionCheckJob DatabaseConnectionCheck { get; set; }
public UnlockDatabaseAccountJob UnlockDatabaseAccount { get; set; }
public LinuxConnectionCheckJob LinuxConnectionCheck { get; set; }
public WindowsConnectionCheckJob WindowsConnectionCheck { get; set; }
public ReplacePasswordInFileJob ReplacePasswordInFile { get; set; }
}
The properties of PostRenewalActionJobs type (AlterDataBaseLink, DatabaseConnectionCheck, etc) can be defined for a specific account or for all accounts by using * as key in the dictionary:
By using below method I am able to retrieve the jobs for an account (if exists) or the general jobs:
public PostRenewalActionJobs GetJobsForAccount(string accountName)
{
return Jobs.ContainsKey(accountName) ? Jobs[accountName] : Jobs["*"];
}
I would like to have a dynamic way of getting a job from the all accounts object ("*") if the one from the specific account is null.
Something like below but whit out repeating the same code for all job types and also a solution that should work when new job types are introduced.
var dbConCheckJob = GetJobsForAccount("someAccount").AlterDataBaseLink;
if(dbConCheckJob == null || !dbConCheckJob.Any())
{
dbConCheckJob = GetJobsForAccount("*").AlterDataBaseLink
}
I was thinking to use some reflection, but I am not sure how to do it.
You don't need to use reflection. You can already determine whether to get the specific jobs for an account or the generic ones, you could then use a Func to get the job you want:
public TJob GetPostJobForAccount<TJob>(string accountName,
Func<PostRenewalActionJobs, TJob> jobSelector) where TJob : JobBase
{
var genericJobs = Jobs["*"];
var accountJobs = Jobs.ContainsKey(accountName) ? Jobs[accountName] : genericJobs;
// Account might be defined but without any job of the given type
// hence selecting from the defaults if need be
return jobSelector(accountJobs) ?? jobSelector(genericJobs);
}
var bobJob = GetPostJobForAccount("bob", x => x.WindowsConnectionCheck);
var aliceJob = GetPostJobForAccount("alice", x => x.UnlockDatabaseAccount);
I found a way to do it, not sure if there is a better way:
public TJob GetPostJobForAccount<TJob>(string accountName)
{
Type type = typeof(PostRenewalActionJobs);
var accountJobs = Jobs[accountName];
var generalJobs = Jobs["*"];
foreach (var item in type.GetProperties())
{
var itemType = item.PropertyType;
var currentType = typeof(TJob);
if (itemType != currentType)
{
continue;
}
var output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(accountJobs, null);
if (output is null)
{
output = (TJob)accountJobs?.GetType()?.GetProperty(item.Name)?.GetValue(generalJobs, null);
}
return output;
}
return default;
}

Unit test to check for unused properties

I have a function that runs through the properties of a class and replaces the keyword between two dollar signs with the same name from a template.
An example of a class:
public class FeedMessageData : IMailObject
{
public string Username { get; private set;}
public string SubscriptionID { get; private set; }
public string MessageTime { get; private set; }
public string Subject { get; private set; }
public FeedMessageData(string username, string subscriptionID, DateTime messageTime)
{
this.Username = username;
this.SubscriptionID = subscriptionID;
this.MessageTime = messageTime.ToShortDateString();
this.Subject = "Feed " + DateTime.Now + " - SubscriptionID: " + this.SubscriptionID;
}
}
And this is the function to replace the template with the properties:
private string mergeTemplate(string template, IMailObject mailObject)
{
Regex parser = new Regex(#"\$(?:(?<operation>[\w\-\,\.]+) ){0,1}(?<value>[\w\-\,\.]+)\$", RegexOptions.Compiled);
var matches = parser.Matches(template).Cast<Match>().Reverse();
foreach (var match in matches)
{
string operation = match.Groups["operation"].Value;
string value = match.Groups["value"].Value;
var propertyInfo = mailObject.GetType().GetProperty(value);
if (propertyInfo == null)
throw new TillitException(String.Format("Could not find '{0}' in object of type '{1}'.", value, mailObject));
object dataValue = propertyInfo.GetValue(mailObject, null);
template = template.Remove(match.Index, match.Length).Insert(match.Index, dataValue.ToString());
}
return template;
}
I'm looking to create a unit test that writes to the console, possible properties that aren't utilized in the template. An example would be if there wasn't a $SubscriptionID$ in the template. I've tried using PropertyInfo, which gives me the properties of the class, but how do I then use this information to check if they have already been used in the template?
Moq (https://github.com/moq/moq4/wiki) provides ways to verify property/method access.
Follow the tutorials on this link for more details. To verify that your properties are being consumed in your template, you can make use of the VerifyGet method, an example below:
[Fact]
public void VerifyAllPropertiesHaveBeenConsumedInTemplate()
{
var mockMailObject = new Mock<IMailObject>();
var template = "yourTemplateOrMethodThatReturnsYourTemplate";
var result = mergeTemplate(template, mockMailObject.Object);
mockMailObject.VerifyGet(m => m.Username, Times.Once);
mockMailObject.VerifyGet(m => m.SubscriptionID, Times.Once);
mockMailObject.VerifyGet(m => m.MessageTime, Times.Once);
mockMailObject.VerifyGet(m => m.Subject, Times.Once);
}

Cosmos DB - Use ARRAY_CONTAINS in LINQ

I have collection of documents in Cosmos DB. Document can have inner array of objects. So model look like this:
public class Document
{
public string Id { get; set; }
public IList<InnerDocument> InnerDocuments { get; set; }
}
public class InnerDocument
{
public string Type { get; set; }
public string Created { get; set; }
}
I need to get all inner documents if at least one of them has certain type.
If I create query like this:
var innerDocument = new InnerDocument()
{
Type = "foo"
};
context.CreateDocumentQuery<Document>(uri, feedOptions)
.Where(d => d.id == "sample" && d.InnerDocuments.Contains(innerDocument));
it translate like this:
SELECT * FROM root
WHERE (root[\"id\"] = "sample"
AND ARRAY_CONTAINS(root[\"innerDocuments\"], {\"type\":\"foo\"}))
but it returns nothing, because no inner document look like this (all inner documents has also Created) so I need to add third parameter to ARRAY_CONTAINS (which tell that only part match on document is enough) so it should look like this:
SELECT * FROM root
WHERE (root[\"id\"] = "sample"
AND ARRAY_CONTAINS(root[\"innerDocuments\"], {\"type\":\"foo\"}, true))
My problem is that I did not figure out how to pass third parameter in linq. I also tried write IEqualityComparer, which always return true but with no effect (well efect was that I got exception..).
Do you have any idea how could I pass that param in linq?
Thanks.
as far as I know, unfortunately there is no LINQ equivalent for the ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr) overload. To achieve your scenarios, for now you can use SQL query. We are currently working on a set of changes that will enable LINQ for this scenario.
Edit: the available alternative is to use the Any operator with the filters on the property you want to match. For example, the SQL filter: ARRAY_CONTAINS(root.addresses, {"city": "Redmond"}, TRUE) is equivalent to this LINQ expression: addresses.Any(address => address.city == "Redmond")
If I understand correctly, you wish to retrieve all documents that have any inner document in the array with a given property value ("foo" in this example).
Normally, you would use .Where(d => d.InnerDocuments.Any(i => i.Type == "foo")), but Any is not supported yet by the Cosmos LINQ provider.
Instead, you can use this construct as a work-around:
context.CreateDocumentQuery<Document>(uri, feedOptions)
.Where(d => d.Id == "sample")
.SelectMany(d => d.InnerDocuments.Where(i => i.Type == "foo").Select(i => d));
According to this thread Microsoft has recently started working on a real Any feature for the Cosmos LINQ provider.
My solution was slightly more of a hack than a solution, but it works temporarily until the full functionality for .Any() exists.
I use Expressions to dynamically build the Where predicate for my documents, allowing me pass in a CosmosSearchCriteria object which has a list of CosmosCriteria objects as below:
public class CosmosCriteria
{
public CosmosCriteria()
{
ContainsValues = new List<string>();
}
public CosmosCriteriaType CriteriaType { get; set; }
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public ConvertedRuleComparitor Comparitor { get; set; }
public DateRange Dates { get; set; }
public List<string> ContainsValues { get; set; }
}
This allows me to query any property of the Contact model by essentially passing in the PropertyName and PropertyValue.
I haven't looked into the other workaround in here to see if I can make it work with my expression tree building, at the minute I can't afford the time to investigate.
public async Task<CosmosSearchResponse<Model.Contact>>
GetContactsBySearchCriteriaAsync(int pageSize, long companyId,
CosmosSearchCriteria searchCriteria, string continuationToken = null)
{
var collectionName = CreateCollectionName(companyId, Constants.CollectionType.Contacts);
var feedOptions = new FeedOptions { MaxItemCount = pageSize };
if (!String.IsNullOrEmpty(continuationToken))
{
feedOptions.RequestContinuation = continuationToken;
}
var collection = UriFactory.CreateDocumentCollectionUri(
Configuration.GetValue<string>(Constants.Settings.COSMOS_DATABASE_SETTING),
collectionName);
IOrderedQueryable<Model.Contact> documents = Client.CreateDocumentQuery<Model.Contact>(
collection,
feedOptions
);
documents = (IOrderedQueryable<Model.Contact>)documents.Where(document => document.deleted != true);
bool requiresConcatenation = false;
foreach (var criteria in searchCriteria.Criteria)
{
switch (criteria.CriteriaType)
{
case Constants.CosmosCriteriaType.ContactProperty:
// This is where predicates for the documents.Where(xxxx)
// clauses are built dynamically with Expressions.
documents = AddContactPropertyClauses(documents, criteria);
break;
case Constants.CosmosCriteriaType.PushCampaignHistory:
requiresConcatenation = true;
break;
}
}
documents = (IOrderedQueryable<Model.Contact>)documents.AsDocumentQuery();
/*
From this point onwards, we have to do some wizardry to get around the fact that there is no Linq to SQL
extension overload for the Cosmos DB function ARRAY_CONTAINS (<arr_expr>, <expr> , bool_expr).
The feature is planned for development but is not yet ready.
Keep an eye on the following for updates:
https://stackoverflow.com/questions/52412557/cosmos-db-use-array-contains-in-linq
https://feedback.azure.com/forums/263030-azure-cosmos-db/suggestions/11503872-support-linq-any-or-where-for-child-object-collect
*/
if (requiresConcatenation)
{
var sqlString = documents.ToString();
var jsonDoc = JsonConvert.DeserializeObject<dynamic>(sqlString); // Have to do this to remove the escaping
var q = (string)jsonDoc.query;
var queryRootAlias = Util.GetAliasNameFromQuery(q);
if (queryRootAlias == string.Empty)
{
throw new FormatException("Unable to parse root alias from query.");
}
foreach (var criteria in searchCriteria.Criteria)
{
switch (criteria.CriteriaType)
{
case Constants.CosmosCriteriaType.PushCampaignHistory:
q += string.Format(" AND ARRAY_CONTAINS({0}[\"CampaignHistory\"], {{\"CampaignType\":1,\"CampaignId\":{1}, \"IsOpened\": true }}, true) ", queryRootAlias, criteria.PropertyValue);
break;
}
}
documents = (IOrderedQueryable<Model.Contact>)Client.CreateDocumentQuery<Model.Contact>(
collection,
q,
feedOptions
).AsDocumentQuery();
}
var returnValue = new CosmosSearchResponse<Model.Contact>();
returnValue.Results = new List<Model.Contact>();
Console.WriteLine(documents.ToString());
var resultsPage = await ((IDocumentQuery<Model.Contact>)documents).ExecuteNextAsync<Model.Contact>();
returnValue.Results.AddRange(resultsPage);
if (((IDocumentQuery<Model.Contact>)documents).HasMoreResults)
{
returnValue.ContinuationToken = resultsPage.ResponseContinuation;
}
return returnValue;
}
Hope this helps, or if someone has a better way, please do tell!
Dave

Check all properties in List

I have List of class as :-
public class Requirement
{
public string Id { get; set; }
public string desc { get; set; }
}
List lstRequirement
I have 3 records in this list for Id and desc.
I wanted to check if any of item is not remaining null.
For that I used below :-
bool IsHavingValidTags = lstRequirement.All(_=> _.Id!=null && _.desc!=null);
This condition is working fine with above Linq.
But I wanted to make it as Generic.
Eg. In future there may get added 5 more properties in Requirement class.
After addition of properties I also have to make changes in Linq.
How can I make this Linq condition generic for all properties?
I want to check any of the property is not remaining null in List.
Please help..!!!
I tried With =>
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
But not giving desired result.
EDIT 1 :
You can write an extension method that uses reflection like the following:
public static class Extensions
{
public static bool AreAllPropertiesNotNullForAllItems<T>(this IEnumerable<T> items)
{
var properties = typeof(T).GetProperties();
return items.All(x => properties.All(p => p.GetValue(x) != null));
}
}
then use like this:
bool IsHavingValidTags = lstRequirement.AreAllPropertiesNotNullForAllItems();
EDIT:
PropertyInfo.GetValue(object obj) method overload was introduced in .NET Framework 4.5. If you are using .NET Framework 4.0 you need to call p.GetValue(x, null)
Instead of this you should make those field not null. this will never allow those field inserted null. keep validations. like bellow.
[Required(ErrorMessage = "First name is required")]
public string first_name { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string last_name { get; set; }
You can use foreach loop to loop through all the object in the list. Then use reflection to get all the properties in each item in the list, then you can loop through those properties to perform your null check.
Foreach (var x in lstRequirement){
List prop = x.GetType().GetProperties();
Foreach (var y in prop){
If (y == null){
IsHavingValidTag = true;
//Then you can return you method here or throw an Exception
}
}
Hope this helps.
You should add an static method to check the Properties of the Class. I will show you the following example.
Instead of your code :
bool IsHavingValidTags = lstRequirement.All(_ => _ != null);
use the following codes:
bool flg = list.All(m => CheckProperties(m));
public static bool CheckProperties<T>(T source)
{
bool rtnFlg = true;
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value == null)
{
return false;
}
}
return rtnFlg;
}

Reflection on IQueryable<T> can't find property

I created a custom gridview control and exported it into a dll so I can reuse it. Inside the dll I created a function to get the DataSource, I'm trying to fill a dropdown from there but is failing.
So on my website I have this
public partial class _Management : System.Web.UI.Page
{
public class _ManagementHelper
{
public int ID;
public string CompanyName;
public string ResourceName;
}
protected void Page_Load(object sender, EventArgs e)
{
ucGridViewEx.DataSource = ucGridViewEx_Source();
ucGridViewEx.DataBind();
}
private List<dynamic> ucGridViewEx_Source()
{
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
ID = x.ResourceID,
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
return ucGridViewEx.GridViewExDataSource(source);
}
Then the custom control inside the dll have this relevant code
public List<dynamic> GridViewExDataSource<T>(IQueryable<T> query)
{
foreach (var column in this.Columns)
{
var gridViewExColumn = column as ColumnEx;
if (gridViewExColumn != null
&& gridViewExColumn.SearchType == SearchTypeEnum.DropDownList)
{
gridViewExColumn.DropDownDataSource = query.GetDropDownDataSource(gridViewExColumn.DataField);
}
}
return ((IQueryable<dynamic>)query).ToList<dynamic>();
}
Function GetDropDownDataSource() is inside another extension class inside the same dll as the gridview
internal static List<ListItem> GetDropDownDataSource<T>(this IQueryable<T> query,
string dataField)
{
var ddlSource = new List<ListItem>();
// x =>
var xParameter = Expression.Parameter(typeof(T), "x");
// x.Property
var propery = typeof(T).GetProperty(dataField);
// x => x.Property
var columnLambda = Expression.Lambda(Expression.Property(xParameter, propery), xParameter);
return ddlSource;
}
Code fails in this where I'm assingning the value to columnLambda because property is null, not because it does not exist (it does) because is not getting any property. I tried with GetProperties() and is not returning anything.
Is curious than this is happening since I moved to the DataSource to select into _ManagementHelper. I was using a dynamic ( Select(x => new {}) ) on ucGridViewEx_Source() before and it worked perfectly. Please don't provide the solution to keep using the dynamic because I need to allow both types, with dynamic and using custom objects.
_ManagementHelper has no property. It just contains three fields (as far as you told us). So GetPrperty returns nothing. Change the members of _ManagementHelper to properties:
public class _ManagementHelper
{
public int ID { get; set; }
public string CompanyName { get; set; }
public string ResourceName { get; set; }
}
I see one bug --
var source = dl.ComapniesResources.Select(x => new _ManagementHelper
{
// ResourceID = x.ResourceID, this was the old code
ID = x.ResourceID, // fixed code
CompanyName = x.Supplier1.SupplierName,
ResourceName = x.Name
});
also, where is ListItem defined?

Categories

Resources