I have the following method that I need to test:
public async Task<SomeClass> GetAsync(string partitionKey, string rowKey)
{
var entities = new List<SomeClass>();
await foreach (var e in _tableClient.QueryAsync<SomeClass>(x => x.PartitionKey == partitionKey && x.RowKey == rowKey))
{
entities.Add(e);
}
return entities.FirstOrDefault();
}
I'd like to setup the _tableClient.QueryAsync() (in the moq) to be able to return different result based on the input parameter. This is important to ensure my unit test covers the logic.
My attempt is:
var thingsToMock = new List<(string PartitionKey, string RowKey, string Value)>() {
("maxCount", "maxCount", "0"),
("maxCount", "xyz", "1000"),
("maxCount", "abc", "2000")
};
var tableClientMock = new Mock<TableClient>();
foreach (var thingToMock in thingsToMock)
{
var returnPage = Page<SomeClass>.FromValues(new List<SomeClass>
{
new SomeClass{ PartitionKey = thingToMock.PartitionKey, RowKey = thingToMock.RowKey, Value = thingToMock.Value }
}, null, new Mock<Response>().Object);
var returnPages = AsyncPageable<SomeClass>.FromPages(new[] { returnPage });
Expression<Func<SomeClass, bool>> exp = (x) => x.PartitionKey == thingToMock.PartitionKey && x.RowKey == thingToMock.RowKey ? true : false;
tableClientMock
.Setup(i => i.QueryAsync<SomeClass>(It.Is<Expression<Func<SomeClass, bool>>>(expression => LambdaExpression.Equals(expression, exp)), null, null, default))
.Returns(returnPages);
}
The issue is that the _tableClientMock doesn't seem to return what I expected when I call GetAsync("maxCount", "abc"). I'd expect with this call, it would pass in the same parameters to tableClient.QueryAsync() method, which in my Mock should return instance of SomeClass with value of 2000. But instead, it throw "Object reference not set to an instance of an object." error.
If I change the tableClientMock setup for QueryAsync to be the following, it somewhat works:
.Setup(i => i.QueryAsync<SomeClass>(It.IsAny<Expression<Func<SomeClass, bool>>>(), null, null, default))
But this will not achieve my objective, which is to be able to pass different parameters (partitionKey and rowKey) to get different result.
I'm using the following NuGet package:
"Azure.Data.Tables" Version="12.7.1"
"moq" Version="4.14.1"
Related
I need to perform a search to one of my Realm table based on a keyword. The search function is done in an async function but it is quite slow: 3-5 seconds to search and show the results for 9000 data.
The search is done in three steps:
Search based on the query and return the list of Guid
Return the list of objects based on list of Guid
Use the result value to update the UI
When I try to return List<IssueTable> directly from SearchIssueInProject, I can't access its property later because "this realm instance has been closed" (something like that).
This is my search functions:
public async Task<List<IssueTable>> GetFilteredIssuesInProject(string query)
{
if (!string.IsNullOrEmpty(query))
{
var searchResults = await Task.Run(() => SearchIssueInProject(query));
return searchResults.Select(i => RealmConnection.Find<IssueTable>(i)).ToList();
}
return this.AllIssuesInProject;
}
List<string> SearchIssueInProject(string query)
{
using (var realm = Realm.GetInstance(RealmConfiguration))
{
Func<IssueTable, bool> searchIssue = d =>
string.IsNullOrEmpty(query) ? true :
Contains(d.Id.ToString(), query) ||
Contains(d.Status.DisplayName, query) ||
Contains(d.Status.Value, query) ||
Contains(d.Team.Name, query) ||
Contains(d.Team.Initial, query) ||
Contains(d.StandardIssueCategory.Title, query) ||
Contains(d.StandardIssueType.Title, query) ||
Contains(d.Drawing.Title, query) ||
Contains(d.Location.Title, query) ||
Contains(d.CreatedBy.DisplayName, query) ||
Contains(d.UpdatedBy.DisplayName, query);
var result = realm.All<IssueTable>().Where(searchIssue)
.OrderByDescending(i => i.UpdatedDate)
.Select(i => i.Guid)
.ToList();
return result;
}
}
public List<IssueTable> GetAllIssues()
{
return RealmConnection.All<IssueTable>()
.OrderByDescending(i => i.UpdatedDate)
.ToList();
}
Contains function:
public static bool Contains(string source, string filter)
{
return source.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
}
And this is how I use the search function:
this.WhenAnyValue(x => x.ViewModel.IssueSearchQuery)
.Throttle(TimeSpan.FromMilliseconds(1000), RxApp.MainThreadScheduler)
.DistinctUntilChanged()
.Do(_ =>
{
this.IssueAdapter.Issues.Clear();
})
.Select(searchTerm =>
{
if (SearchingProgressDialog == null && Activity != null)
{
ShowLoadingProgress();
}
var result = this.ViewModel.GetFilteredIssuesInProject(searchTerm);
return result
.ToObservable()
.ObserveOn(RxApp.MainThreadScheduler);
})
.Switch()
.Subscribe(searchResult =>
{
this.IssueAdapter.Issues.AddRange(searchResult);
this.IssueAdapter.NotifyDataSetChanged();
if (SearchingProgressDialog != null)
{
SearchingProgressDialog.Dismiss();
SearchingProgressDialog = null;
}
});
How to improve the search function?
Because you're creating a function and not an expression, the query isn't evaluated by Realm, but rather by LINQ to objects which means every object needs to be fetched by Realm, then the accessed properties will be fetched one by one to be used by the comparison. Unfortunately, all the fields you search by need traversing a related object which is not yet supported by Realm .NET.
One approach to resolve this would be to denormalize your data and copy these properties on the IssueTable object. Then you can execute a query like:
var result = realm.All<IssueTable>()
// Note that the .Where overload we select uses Expression<Func<>>
// Also, StatusDisplayName is a copy of Status.DisplayName
.Where(i => i.StatusDisplayName || ...)
.OrderByDescending(i => i.UpdatedDate)
.ToArray()
.Select(i => i.Guid)
.ToList();
With Amazon native .net lib, batchget is like this
var batch = context.CreateBatch<MyClass>();
batch.AddKey("hashkey1");
batch.AddKey("hashkey2");
batch.AddKey("hashkey3");
batch.Execute();
var result = batch.results;
Now I'm testing to use servicestack.aws, however I couldn't find how to do it. I've tried the following, both failed.
//1st try
var q1 = db.FromQueryIndex<MyClass>(x => x.room_id == "hashkey1" || x.room_id == "hashkey2"||x.room_id == "hashkey3");
var result = db.Query(q1);
//2nd try
var result = db.GetItems<MyClass>(new string[]{"hashkey1","hashkey2","hashkey3"});
In both cases, it threw an exception that says
Additional information: Invalid operator used in KeyConditionExpression: OR
Please help me. Thanks!
Using GetItems should work as seen with this Live Example on Gistlyn:
public class MyClass
{
public string Id { get; set; }
public string Content { get; set; }
}
db.RegisterTable<MyClass>();
db.DeleteTable<MyClass>(); // Delete existing MyClass Table (if any)
db.InitSchema(); // Creates MyClass DynamoDB Table
var items = 5.Times(i => new MyClass { Id = $"hashkey{i}", Content = $"Content {i}" });
db.PutItems(items);
var dbItems = db.GetItems<MyClass>(new[]{ "hashkey1","hashkey2","hashkey3" });
"Saved Items: {0}".Print(dbItems.Dump());
If your Item has both a Hash and a Range Key you'll need to use the GetItems<T>(IEnumerable<DynamoId> ids) API, e.g:
var dbItems = db.GetItems<MyClass>(new[]{
new DynamoId("hashkey1","rangekey1"),
new DynamoId("hashkey2","rangekey3"),
new DynamoId("hashkey3","rangekey4"),
});
Query all Items with same HashKey
If you want to fetch all items with the same HashKey you need to create a DynamoDB Query as seen with this Live Gistlyn Example:
var items = 5.Times(i => new MyClass {
Id = $"hashkey{i%2}", RangeKey = $"rangekey{i}", Content = $"Content {i}" });
db.PutItems(items);
var rows = db.FromQuery<MyClass>(x => x.Id == "hashkey1").Exec().ToArray();
rows.PrintDump();
I know that Servicestack.Ormlite is setup to be a 1:1 mapping between poco and database table. I have a situation where I will have groups of tables that are of the same structure and they are created as necessary. I am trying to find a way to be able to do something where I can continue to use the IDbConnection and specify the table name in CRUD operations.
Something like
using(var db = _conn.OpenDbConnection()){
db.SaveAll(objList, "DIFFERENT_TABLE");
}
I was easily able to work around to make creating and deleting the tables. I am hoping that I can use of the ExpressionVisitor or something else to help change the table name before it is executed. One of the requirements of the project is that it be database agnostic, which is why I am trying to not manually write out the SQL.
Solutions
Here are a couple of functions that I ended up creating if anyone out there wants some more examples.
public static List<T> SelectTable<T>(this IDbConnection conn, string tableName) {
var stmt = ModelDefinition<T>.Definition.SqlSelectAllFromTable;
stmt = stmt.Replace(ModelDefinition<T>.Definition.Name, tableName.FmtTable());
return conn.Select<T>(stmt);
}
public static List<T> SelectTableFmt<T>(this IDbConnection conn, string tableName, string sqlFilter,
params object[] filterParams) {
var stmt = conn.GetDialectProvider().ToSelectStatement(typeof (T), sqlFilter, filterParams);
stmt = stmt.Replace(ModelDefinition<T>.Definition.Name, tableName.FmtTable());
return conn.Select<T>(stmt);
}
public static void InsertTable<T>(this IDbConnection conn, T obj, string tablename) {
var stmt = conn.GetDialectProvider().ToInsertRowStatement(null, obj);
stmt = stmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(stmt);
}
public static int SaveAll<T>(this IDbConnection conn, string tablename, IEnumerable<T> objs) {
var saveRows = objs.ToList();
var firstRow = saveRows.FirstOrDefault();
if (Equals(firstRow, default(T))) return 0;
var defaultIdValue = firstRow.GetId().GetType().GetDefaultValue();
var idMap = defaultIdValue != null
? saveRows.Where(x => !defaultIdValue.Equals(x.GetId())).ToSafeDictionary(x => x.GetId())
: saveRows.Where(x => x.GetId() != null).ToSafeDictionary(x => x.GetId());
var existingRowsMap = conn.SelectByIds<T>(tablename, idMap.Keys).ToDictionary(x => x.GetId());
var modelDef = ModelDefinition<T>.Definition;
var dialectProvider = conn.GetDialectProvider();
var rowsAdded = 0;
using (var dbTrans = conn.OpenTransaction()) {
foreach (var obj in saveRows) {
var id = obj.GetId();
if (id != defaultIdValue && existingRowsMap.ContainsKey(id)) {
var updStmt = dialectProvider.ToUpdateRowStatement(obj);
updStmt = updStmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(updStmt);
}
else {
if (modelDef.HasAutoIncrementId) {}
var stmt = dialectProvider.ToInsertRowStatement(null, obj);
stmt = stmt.Replace(obj.GetType().Name, tablename.FmtTable());
conn.ExecuteSql(stmt);
rowsAdded++;
}
}
dbTrans.Commit();
}
return rowsAdded;
}
OrmLite supports specifying the table name for Update and Delete operations. Unfortunately the examples in the readme here have yet to be updated. This is the required format:
UPDATE:
db.UpdateFmt(table: "Person", set: "FirstName = {0}".Fmt("JJ"), where: "LastName = {0}".Fmt("Hendrix"));
DELETE:
db.DeleteFmt(table: "Person", where: "Age = {0}".Fmt(27));
The methods you need can be found here. You should be able to use .Exec to handle reading and insert operations.
In my current project I need write in a table all values are changed in the application.
Ex. the guy update the UserName, I need put in a table UserName old value "1" new value "2".
I tried use the ObjectStateEntry but this return all fields. I think the FW return all because my code.
public USER Save(USER obj)
{
using(TPPTEntities db = new TPPTEntities())
{
db.Connection.Open();
USER o = (from n in db.USERs where n.ID == obj.ID select n).FirstOrDefault();
if (o == null)
{
o = new USER()
{
BruteForce = 0,
Email = obj.Email,
IsBlock = false,
LastLogin = DateTime.Now,
Name = obj.Name,
UserName = obj.UserName,
UserPassword = new byte[0],
};
db.AddToUSERs(o);
}
else
{
o.Email = obj.Email;
o.Name = obj.Name;
o.UserName = obj.UserName;
}
db.SaveChanges();
db.Connection.Close();
}
return obj;
}
A way to get old and new values is this:
var ose = this.ObjectStateManager.GetObjectStateEntry(o.EntityKey);
foreach (string propName in ose.GetModifiedProperties())
{
string.Format("Property '{0}', old value: {1}, new value: {2}",
propName, ose.OriginalValues[propName], ose.CurrentValues[propName]);
}
This is pretty useless, of course, but I'm sure you'll know what to do in the foreach loop to store the changes.
Is this a WCF Service? In that case, the changes will probably never come trough since changes to the Object Graph are made where the Object Context is not available. Consider using Self-Tracking Entities
I am building an asp.net site in .net framework 4.0, and I am stuck at the method that supposed to call a .cs class and get the query result back here is my method call and method
1: method call form aspx.cs page:
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
2: Method in helper class:
public IQueryable<VariablesForIQueryble> GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some connection_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where (gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)
select new VariablesForIQueryble(m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//select new {m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap};
return query ;
}
I tried the above code with IEnumerable too without any luck. This is the code for class VariablesForIQueryble:
3:Class it self for taking anonymouse type and cast it to proper types:
public class VariablesForIQueryble
{
private int _emailCap;
public int EmailCap
{
get { return _emailCap; }
set { _emailCap = value; }
}`....................................
4: and a constructor:
public VariablesForIQueryble(int memberID, string memberFirst, string memberLast, string memberEmail, int? validEmail, int? emailCap)
{
this.EmailCap = (int) emailCap;
.........................
}
I can't seem to get the query result back, first it told me anonymous type problem, I made a class after reading this: link text; and now it tells me constructors with parameters not supported. Now I am an intermediate developer, is there an easy solution to this or do I have to take my query back to the .aspx.cs page.
If you want to project to a specific type .NET type like this you will need to force the query to actually happen using either .AsEnumerable() or .ToList() and then use .Select() against linq to objects.
You could leave your original anonymous type in to specify what you want back from the database, then call .ToList() on it and then .Select(...) to reproject.
You can also clean up your code somewhat by using an Entity Association between Groups and Members using a FK association in the database. Then the query becomes a much simpler:
var result = ctx.Members11.Include("Group").Where(m => m.Group.groupID == incomingGroupID && m.EmailCap == incomingEmailCap);
You still have the issue of having to do a select to specify which columns to return and then calling .ToList() to force execution before reprojecting to your new type.
Another alternative is to create a view in your database and import that as an Entity into the Entity Designer.
Used reflection to solve the problem:
A: Query, not using custom made "VariablesForIQueryble" class any more:
//Method in helper class
public IEnumerable GetQuery(int incomingGroupID, int incomingEmailCap)
{
var ctx = new some_Connection();
ObjectSet<Members1> members = ctx.Members11;
ObjectSet<groupMember> groupMembers = ctx.groupMembers;
var query = from m in members
join gm in groupMembers on m.MemberID equals gm.MemID
where ((gm.groupID == incomingGroupID) && (m.EmailCap == incomingEmailCap)) //select m;
select new { m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap };
//select new VariablesForIQueryble (m.MemberID, m.MemberFirst, m.MemberLast, m.MemberEmail, m.ValidEmail, m.EmailCap);
//List<object> lst = new List<object>();
//foreach (var i in query)
//{
// lst.Add(i.MemberEmail);
//}
//return lst;
//return query.Select(x => new{x.MemberEmail,x.MemberID,x.ValidEmail,x.MemberFirst,x.MemberLast}).ToList();
return query;
}
B:Code to catch objects and conversion of those objects using reflection
helper cls = new helper();
var query = cls.GetQuery(GroupID,emailCap);
if (query != null)
{
foreach (var objRow in query)
{
System.Type type = objRow.GetType();
int memberId = (int)type.GetProperty("MemberID").GetValue(objRow, null);
string memberEmail = (string)type.GetProperty("MemberEmail").GetValue(objRow, null);
}
else
{
something else....
}