First of all, apologies if I posted it in the wrong place, I'm new here and I'm not sure if I posted in the right place.
Well, I'm trying to build a generic search method, where I'll add search parameters to mount a SQL Query and execute it on the database. All that using C#. My goal is that the parameter corresponding to the field I'll search, to be a property of the class the method is in. For example:
public foo
{
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public void AddSearchParameter(???, EnumOperator Operator, object Value)
}
Whenever I want to specify a parameter to add on the search, I would like it to look like this:
foo xxx = new foo();
xxx.AddSearchParameter(foo.CustomerCode, EnumOperator.Equal, txtCustomerCode.text);
My question is how to do it?
If you are trying to pass the member information (so that the AddSearchParameter can inspect the MemberInfo and write suitable SQL), then you'd need to use either a string literal (i.e. "CustomerCode"), or an expression tree. The latter is richer, but involves learning the Expression<T> API. But fundamentally:
public void AddSearchParameter(Expression<Func<object>> expression, ...)
...
xxx.AddSearchParameter(() => foo.CustomerCode, ...)
This, however, is not a trivial area of .NET.
If I were doing something like this, I would probably make the Search() method on foo check for the existence of values in the various this properties, and then build the query based on that.
public List<Results> Search()
{
if (!String.IsNullOrEmpty(this.CustomerCode))
{
// add search value to query
}
// etc.
}
Related
I'm using SpecFlow for the very first time to write tests for my project and I ran into a small problem.
I have the next class:
public class FancyName
{
[DataMember]
public Guid Guid { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public List <Country> Countries { get; set; }
}
And I want to generate this class in my Tests using SpecFlow helpers.
Here is the part of Scenario:
[...]
When i add some names
| Name | Countries |
| UK | 1 |
| US | 2 |
[...]
I try to parse it in step definitions like this:
[When(#"I add some names")]
public void AddNames(Table table)
{
var names = table.CreateSet<FancyName>();
[...]
}
And I'm running into 2 problems:
I do not pass the Guid because a want to generate it like Guid.NewGuid() so created object contain null
I pass countries as sorting but i need to create List<Country>().
I used to try iterate through Table and create FancyName objects manually but as I understand it is not SpecFlow way. I tried to look through documentation and wasn't lucky to find proper solution.
May be somebody know the really good way to solve this?
Thanks in advance.
The main SpecFlow.Assist author here...
I agree with #sam-holder above that Table.CreateSet<T> is not magic, and perhaps the transform is an easier solution in this case. But Assist does, in deed, have the features needed to do what is asked. :)
I'd like to explain how. I see two issues presented as:
1) How to set the Guid on each record?
2) How to convert the string value to an array?
For (1), the answer is pretty simple. You can pass a function to CreateSet that tells the library how to instantiate the object you want to create. Since you just want to set the Guid, you can do it like so:
table.CreateSet<FancyName>(() => new FancyName { Guid = Guid.NewGuid()});
// or simpler, table.CreateSet(() => new FancyName { Guid = Guid.NewGuid()});
For (2), you'll have to do a little more programming. You want Assist to know how to convert the string to a list. To do this, you'll have to create a "value retriever" and register it with Assist. I was able to do this with the following code:
public class CountryRetriever : IValueRetriever
{
public bool CanRetrieve(KeyValuePair<string, string> keyValuePair, Type targetType, Type propertyType)
{
return propertyType.FullName.StartsWith("System.Collections.Generic.List`1[[SpecFlowExample.Country");
}
public object Retrieve(KeyValuePair<string, string> keyValuePair, Type targetType, Type propertyType)
{
return keyValuePair.Value.Split(',')
.Select(x => new Country {Name = x})
.ToList();
}
}
[Binding]
public class Steps
{
[BeforeTestRun]
public static void Setup()
{
TechTalk.SpecFlow.Assist.Service.Instance.RegisterValueRetriever(new CountryRetriever());
}
[When(#"i add some names")]
public void WhenIAddSomeNames(Table table)
{
var things = table.CreateSet<FancyName>(() => new FancyName { Guid = Guid.NewGuid()});
}
}
Pointing directly at a list like this, especially using a string, is pretty hacky, but this code does work.
The country retriever will announce that it can handle the transformation of a string when it is confronted with a List<Country> type. It then splits the string and creates a list of countries.
SpecFlow starts each test run with value retrievers for most of the base .Net types, but not your List<Country>. To accomodate your type, you'll need to register your new value retriever before every test run.
Table.CreateSet<> can't perform magic. It can't know that its supposed to create a new Guid for your object, or that its supposed to create a list containing 2 countries. You'll have to create this object yourself I think.
The best way to solve this is to use a [StepArgumentTransformation]
something like this:
[StepArgumentTransformation]
public List<FancyName> TransformToFancyName(Table table)
{
//create the list from the table contents
}
[When(#"I add some names")]
public void AddNames(List<FancyName> names)
{
.. use your FancyNames here
}
specflow will call your StepArgumentTransformation for any Step which has an argument of List<FancyName> as the last parameter and a corresponding table in the feature
You can think about something like Nested Tables?, but according to this post it's a bad idea. It suggests to introduce additional step to populate complex objects.
I have a class in one application - that I cannot change (legacy) - that is inside of a assembly (DLL file):
public class ShippingMethod
{
public string ShipMethodCode { get; set; }
public string ShipMethodName { get; set; }
public decimal ShippingCost { get; set; }
public List<ShippingMethod> GetAllShippingMethods()
{
......
}
}
I have a second application that is referencing that assembly (DLL file) and needs to populate a drop-down with all the Shipping Methods. Ex: "UPS - $3.25"
The issue is that it needs to be using the correct format for different currencies. Ex: $3.25 or 3.25€ depending on a parameter called CountryID.
I have written a function String DisplayMoney(Decimal Amount, Integer CountryID) that will return the correct format of the amount.
Now I need to apply this function to every shipping method and save it into a new list.
What is the best way to do this?
I can create another class called LocalizedShippingMethods as follows:
public class LocalizedShippingMethod
{
public ShippingMethod ShipMethod { get; set; }
public string LocalizedShippingCost { get; set; }
}
Is this the best way to accomplish this? Is there a better way to do this using inheritance? And if I use inheritance, how do I get the values from the first LIST into the NEW LIST?
That is indeed a good method of doing it. You can use a pretty quick Linq query to pull the old List into the new one:
List<LocalizedShippingMethod> Translate(List<ShippingMethod> oldList)
{
return oldList.Select(a => new LocalizedShippingMethod
{
// Initialize properties according to however you translate them
}).ToList();
}
Additionally, to make this more streamlined and obvious, you could do any of the following to aid in the translation:
Create a constructor for LocalizedShippingMethod that takes in a ShippingMethod and properly sets the properties
Create a static method on LocalizedShippingMethod that takes in a ShippingMethod and returns an initialized LocalizedShippingMethod
Create an operator on LocalizedShippingMethod that converts from a ShippingMethod
Create an extension method on ShippingMethod, call it ToLocalized() that returns a LocalizedShippingMethod
What if you create an extension method for the ShippingMethod class?
The best way to do this is whatever way works best for you. If you're the person who's going to have to maintain this code, what will make your life the easiest down the road?
Once you've answered that question, that is the best solution.
Basically, I would like to create a Method, that takes a base-class as a parameter, and can be used "generic" for derived classes
ef-code-first classes:
the base class
public abstract class BaseClass
{
public int Id { get; set; }
public string Name { get; set; }
}
derived classes:
public class DerivedA:BaseClass
{
public string AValue {get;set;}
...more specific fields
}
public class DerivedB:BaseClass
{
public string BValue {get;set;}
..... more specific fields
}
I call a "generic Method" with these slightly different objects:
System.Data.Entity.DbSet<DerivedA> _dA....
System.Data.Entity.DbSet<DerivedB> _dB....
genericMethod(_dA.Where(a => a.Name.StartsWith("a name")))); //<-contains records
genericMethod(_dB.Where(a => a.Id==5)); //<---- contains records
Both "Where..." contain records in debug (after clicking on Enumerate)
now the method:
public string genericMethod(<IQueryable>BaseClass _myClass)
{
foreach (BaseClass c in _myClass) // <-------class is empty - no records
{
// do something usefull...
}
return someResult
}
But no records are contained, when inside the method.
Is it possible, what I am trying to do...?
Does it make sense?
There are no design-time or compile-time or runtime errors, but the passed object contains no records when passed to the method, but it contained records in the calling statement.
What did I do wrong?
Is there a better approach? -
I need this Method, for manipulation of more than two (maybe ten) derived classes, and therefor I want it "generic".
Thank you!
When faced with something like this, I like to simplify my code.
I would try removing the _dA.Where(a => a.Name.StartsWith("a name")) and _dB.Where(a => a.Id==5) from the method call and put them into variables first (and then pass the variable into the method).
This will allow you to better inspect your code and perhaps shed light on the problem.
add .ToList() to materialize the query before you pass it to the method:
genericMethod(_dA.Where(a => a.Name.StartsWith("a name"))).ToList());
Otherwise you're not really passing the result of the query, you're just passing a query that needs to be evaluated first. ToList() will evaluate it for you. When you look in the debugger watch, it's basically evaluating it for you on the fly, that's why you see rows returned.
After that, change your method to deal with IList instead of IQueryable.
I have a datacontext, and it has Authors table.
public partial Author:IProductTag{}
I want to cast Table<Authors> object to Table<IProductTag>, but that appears to be impossible. I am trying to do that because I want my method to be able to work with different tables which come as input parameters. To be more specific, I need to execute OrderBy and Select methods of the table. I have few other tables, entities of which implement IProductTag . Also, I tried to write a function like:
public static void MyF<t>(){
Table<t> t0 = (Table<t>)DataContext.GetMyTableUsingReflection();
}
But it fails at compile-time. And if I cast the table to something like ITable or IQueriable, then the OrderBy and Select functions simply don't work. So how do you deal with it?
I suspect you want to make your method generic - so instead of
void DoSomethingWithTable(Table<IProductTag> table)
you should have
void DoSomethingWithTable<T>(Table<T> table) where T : class, IProductTag
That should work fine, assuming you only need to read entities (and apply query operators etc). If that doesn't work for you, please give more details about what your method needs to do.
(You say that your attempt to use reflection failed, but you haven't said in what way it failed. Could you give more details?)
I have no idea what a ProductTag is so I've used different types to show my solution to this problem. Yes there doesn't seem to be a way to get a Table<T>, but you can get IQueryable<T> which works just as well (at least for my situation).
I have a simple analytics database, where each website has its own table containing both generic and specific items. I wanted to use an interface for the shared data.
public interface ISession
{
public DateTime CreateDt {get; set; }
public string HostAddress {get; set; }
public int SessionDuration {get; set; }
}
public static IQueryable<ISession> GetQueryableTable(MyDataContext db, string site)
{
Type itemType;
switch (item)
{
case "stackoverflow.com":
itemType = typeof(Analytics_StackOverflow);
break;
case "serverfault.com":
itemType = typeof(Analytics_ServerFault);
break;
default: throw Exception();
}
return db.GetTable(itemType).Cast<ISession>();
}
You can then do a query like this :
var table = GetQueryableTable(db, "stackoverflow.com");
var mySessions = table.Where(s => s.HostAddress == MY_IP);
To create a new row you can use reflection :
var rowType = typeof(Analytics_ServerFault);
var newRow = (ISession) rowType.GetConstructor(new Type[0]).Invoke(new object[0]);
(I have a function to get GetRowType - which is not shown here).
Then to insert into the table I have a separate helper function:
public static void Insert(MyDataContext db, ISession item)
{
// GetTable is defined by Linq2Sql
db.GetTable(GetRowType(domain)).InsertOnSubmit(item);
}
Does NewExpression.Members inform the LINQ runtime how to map a type's constructor parameters to its properties? And if so, is there an attribute to set the mapping? I'm imagining something like this:
public class Customer
{
public Customer(int id, string name)
{
Id = id;
Name = name;
}
[CtorParam("id")]
public int Id { get; set; }
[CtorParam("name")]
public string Name { get; set; }
}
But none of the MSDN docs really inform you how exactly Members is initialized.
My limited understanding is that you don't usually need to pass the member information; the arguments are taken (by position) from the arguments parameter. The member info is (I suspect) intended to help some internal APIs when dealing with things like anonymous-types, which look (in C#) like they are initialized by member (like an object-initializer), but which are actually initialized by constructor. This means things like LINQ-to-SQL will see a constcutor use, and then (in the next part of the query) access to obj.Name - it needs a way to understand that this means "the 3rd parameter to the constructor (which never actually gets called). In particular for things like groupings.
So this is fine:
var param = Expression.Parameter(typeof(string), "name");
var body = Expression.New(typeof(Customer).GetConstructor(new[] {typeof(int), typeof(string)}),
Expression.Constant(1), param);
var func = Expression.Lambda<Func<string, Customer>>(body, param).Compile();
var cust = func("abc");
If you do need them, I would expect them to be positional relative to the "arguments" expressions - so you would pass in (in an array) the member for id and name. Note that there is also a separate expression for intialzer-style binding.