Thanks to a patch submission by Hakan Forss the Object Hydrator Fluent Interface is now type safe, with all the magic strings removed. The standard inferences are still there so you could do most of your generation without ever using the advanced methods.
The new syntax for the example in my last post looks like this:
string defaultValue = "Testing123";
Hydrator<SimpleCustomer> hydrator = new Hydrator<SimpleCustomer>()
.With(x => x.Description, defaultValue);
SimpleCustomer customer = hydrator.GetSingle();
For the second example where you wanted to override the Company name generation, you actually have two choices:
Hydrator<SimpleCustomer> hydrator = new Hydrator<SimpleCustomer>()
.WithLastName(x => x.Company);
SimpleCustomer customer = hydrator.GetSingle();
Hydrator<SimpleCustomer> hydrator2 = new Hydrator<SimpleCustomer>()
.With(x => x.Company, new FirstNameGenerator());
SimpleCustomer customer2 = hydrator.GetSingle();
Just kind of depends on what your style is...both work the same.
More to come as we creep on on a release. Feel free to check this version (.5) out at http://objecthydrator.codeplex.com
*fixed url
Ryan
Since last I posted, I've received an awesome implementation of a fluent interface for Object Hydrator from Scott Monnig. We've ditched the Attributes and mapping scenarios in favor of some convention and a fluent interface.
So as before this will get you a single customer:
Hydrator<SimpleCustomer> hydrator = new Hydrator<SimpleCustomer>();
SimpleCustomer customer = hydrator.GetSingle();
The difference here is that SimpleCustomer looks slimmer like this:
public string FirstName { get; set; }
public string LastName { get; set; }
public string Company { get; set; }
public string Description { get; set; }
public int Locations { get; set; }
public DateTime IncorporatedOn { get; set; }
public Double Revenue { get; set; }
By convention FirstName, fname etc...will use the FirstNameGenerator...and so on. If there is no infered generator, it will pick one by type.
If you want to override a value, you do it like this:
string defaultValue = "Testing123";
Hydrator<SimpleCustomer> hydrator = new Hydrator<SimpleCustomer>()
.WithDefault("Description", defaultValue);
Easy huh?
What if you wanted to override the Company name with a FirstNameGenerator value for some wacky reason? Piece of cake...
Hydrator<SimpleCustomer> hydrator = new Hydrator<SimpleCustomer>()
.FromGenerator("Company", new FirstNameGenerator());
SimpleCustomer customer = hydrator.GetSingle();
Aggregate roots are still being worked on and are in this code they require a little deeper info, but check out the source if you are curious where we're going with those.
Please check out the source at http://objecthydrator.codeplex.com and peruse the tests to see more examples. I'll be doing a deeper write up as we make that first .1 release and will include the source.
Thanks a ton to Scott and as always feel free to let me know what you think.
You can pass values into either the Attribute or the Attribute map, and override the result from the generator. This allows you to fake a search result.
So going back to the attribute method and building on the previous example...if I add a value of "Smith" to the attribute "LastName" like so:
[LastName("Smith")]
public string LastName { get; set; }
And Re-run it all of my results will have the last name of "Smith"
Similarly with the Attribute Map method I can set the GeneratorDefaultValue property to "Smith" and get the same results.
TTFN!
You can accomplish the same as the above but without using the Attribute decoration method, but rather a collection of Attributes.
Building on the above example, remove all the attributes from FakeCustomer.cs so it looks like this:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5
6
7 namespace SampleHydrator.Models
8 {
9 public class FakeCustomer
10 {
11
12 public string FirstName { get; set; }
13
14 public string LastName { get; set; }
15
16 public string Address { get; set; }
17
18 public string City { get; set; }
19
20 public string State { get; set; }
21
22 public string Zip { get; set; }
23
24 public string Phone { get; set; }
25
26 }
27 }
Next replace your FakeRepository AllCustomers() method with the following:
11 public IList<FakeCustomer> AllCustomers()
12 {
13 FillMe<FakeCustomer> generator = new FillMe<FakeCustomer>();
14 IList<AttributeMap> mymap = new List<AttributeMap>();
15 AttributeMap attmap;
16 attmap = new AttributeMap();
17 attmap.GeneratorName = "FirstName";
18 attmap.PropName = "FirstName";
19 mymap.Add(attmap);
20 attmap = new AttributeMap();
21 attmap.GeneratorName = "LastName";
22 attmap.PropName = "LastName";
23 mymap.Add(attmap);
24 attmap = new AttributeMap();
25 attmap.GeneratorName = "AmericanAddress";
26 attmap.PropName = "Address";
27 mymap.Add(attmap);
28 attmap = new AttributeMap();
29 attmap.GeneratorName = "AmericanCity";
30 attmap.PropName = "City";
31 mymap.Add(attmap);
32 attmap = new AttributeMap();
33 attmap.GeneratorName = "AmericanState";
34 attmap.PropName = "State";
35 mymap.Add(attmap);
36 attmap = new AttributeMap();
37 attmap.GeneratorName = "AmericanPostalCode";
38 attmap.PropName = "Zip";
39 attmap.GeneratorDefaultValue = "False";
40 mymap.Add(attmap);
41 attmap = new AttributeMap();
42 attmap.GeneratorName = "AmericanPhone";
43 attmap.PropName = "Phone";
44 mymap.Add(attmap);
45 return generator.GetList(20, mymap);
46 }
It's a bit more verbose, but allows you to keep your model free of Attributes if you wish. There's more work to be done for sure...but there's the sneak peek.
I'll be doing this in ASP.NET MVC.
WARNING: THIS IS NOT INTENDED TO SHOW BEST PRACTICES OF ASP.NET MVC
First download and compile Object Hydrator.
Create a new ASP.NET MVC project and add a reference to Foundation.ObjectHydrator from the newly created DLL from the above step.
Create a new class in your Models folder that looks like this:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using Foundation.ObjectHydrator.GeneratorTypes;
6
7 namespace SampleHydrator.Models
8 {
9 public class FakeCustomer
10 {
11 [FirstName("")]
12 public string FirstName { get; set; }
13
14 [LastName("")]
15 public string LastName { get; set; }
16
17 [AmericanAddress("")]
18 public string Address { get; set; }
19
20 [AmericanCity("")]
21 public string City { get; set; }
22
23 [AmericanState("")]
24 public string State { get; set; }
25
26 [AmericanPostalCode(false)]
27 public string Zip { get; set; }
28
29 [AmericanPhone("")]
30 public string Phone { get; set; }
31
32 }
33 }
Next Create a class in your Models folder called FakeCustomerRepository that looks like this:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using Foundation.ObjectHydrator;
6
7 namespace SampleHydrator.Models
8 {
9 public class FakeCustomerRepository
10 {
11 public IList<FakeCustomer> AllCustomers()
12 {
13 FillMe<FakeCustomer> generator = new FillMe<FakeCustomer>();
14 return generator.GetList(20);
15 }
16 }
17 }
Next...modify the Index method of your HomeController to look like this:
13 public ActionResult Index()
14 {
15 FakeCustomerRepository fcr = new FakeCustomerRepository();
16 ViewData.Model = fcr.AllCustomers();
17 return View();
18 }
Finally change your /Views/Home/Index.asp to look like this:
<%
@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<SampleHydrator.Models.FakeCustomer>>" %>
<asp
:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp
:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2><ul><%foreach(SampleHydrator.Models.FakeCustomer fc in Model)
{
%>
<li>
<%=fc.LastName %>,<%=fc.FirstName %>
<br
/><%=fc.Address %>
<br
/> <%=fc.City %>, <%=fc.State %> <%=fc.Zip %>
<br
/><%=fc.Phone %></li>
<%
} %> </ul>
</asp
:Content>
Now browse to your default page and you should see a list of Random Customers...
First off...long time since I've last written...yeah I guess frequent blogging isn't my thing.
But I wanted to post this to solicit feedback from the community about something I've been thinking about lately.
Call it DDD, BDD, TDD or a bananna there is a lot of merit in approaching a new application, from an angle other than the database and how you're going to persist the data. If you've been building applications long enough, you know that data access (in any form) is one of the most repetitive and code heavy features of your application. I called it a feature because it really is...and with all features, you're smart to delay the building of the feature until last responsible moment. This allows you to garner the maximum knowledge of said feature.
What if we could build 'working' applications, that demonstrates the solution to the particular business challenge we are trying to solve...*without* a database at all. Well at least until it makes sense to.
Rob Conery's SubSonic has a fantastic feature called the SimpleRepository. This allows you to save any object into a database without having previously created a table and without having done the alphabetical gymnastics of mapping said object to the database. It just saves it. This mimic's closely the concept of the object centric database functionality of db4O etc... I think it's brilliant but what if we could delay that decision even longer still.
What if we could design our object, and pass it through a ''generator" that returned us that object filled with data that mimicked real world data. What if it could return a collection of these objects? Theoretically we could be repositories that acted like databases, but since we hadn't yet commited to a database structure we have the flexibility of tweaking those objects at will.
What we end up with is an interface and business objects that behave like they should, that demonstrate the intent of the application without having to commit to a persistence strategy until nearly the end of the project.
I didn't have any luck finding this out there already...so to that end I started Object Hydrator...it's free and up on CodePlex in source form. It will allow you to create data filled objects during runtime. I think it could have a lot promise when it comes to TDD and DDD/BDD/R2-D2. It currently works with two methods...you can add attributes to your object's properties, or you can use a mapping collection.
So if it interests you, feel free to go check it out and look at the tests to see it in action. It's still a veritable infant, so keep that in mind, but please let me know what you think about its usefulness and the code itself.
Have you ever needed to say replace the location of your images in your application from one location to another, or had a database routine that ran nightly and you wanted to gracefully control access to your application during that process? In this article I am going to show how I approached this on one of my projects and have been pretty happy with it so far.
Here's the 10,000 foot view of the solution. Build a SQL table that has 2 varchar(255) columns, one named AppVarName and the other AppVarValue. Build a class that checks to see if the DataTable exists in the cache, if not retrieve the DataTable from SQL and place it in the cache. The class then loops through the rows in the DataTable creating and assigning application variables with the AppVarName field from the table and populates it's value from the AppVarValue field. Place a call to this class in the Application_start, and Application_BeginRequest methods in the Global.asax. Now in all your pages you have access to this information.
I personally use this in my Master page, in the Page_Load method I check the value of "ApplicationStatus" which I've created in my table and assigned a value of "1" to. If the value is "1" I do nothing, if the value is "0" I redirect to a friendly "Down For Maintenance" page. This allows me to set the value in say a reindexing routine and then change it back when it's finished. I also use different status values to enable or disable certain features of my application, this allows me a lot of flexibility.
Now on to the code snippets. For this article I am using the Enterprise Library Data Access Application Block, if you don't use this it's not a big deal and I think you can see how to adapt this to your own data access pattern. I'll attach the code to the post, but here's the "gist" of it...please pardon any skipped steps...refer to the attached solution for the details.
First make a table called ApplicationSettings with 2 varchar(255) columns one named AppVarName and the other named AppVarValue.
Next make a stored procedure named GetApplicationSettings which reads simply like
SELECT AppVarName, AppVarValue From ApplicationSettings
Now create a blank solution. Add a C# Class Library Project, and a C# ASP.NET web site.
Add the AppManager Class to your Library:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Data;
using
System.Web;
using
Microsoft.Practices.EnterpriseLibrary.Data;
using
System.Data.Common;
namespace
Web.AppManager
{
public class ApplicationVariableManager
{
public void UpdateApplicationVars()
{
string cacheKey = "ApplicationVariables";
int cacheDuration = 0;DataTable dtVariables = HttpRuntime.Cache[cacheKey] as DataTable;if (dtVariables == null)
{
Database myDatabase = DatabaseFactory.CreateDatabase("myConnectionString");using (DbCommand dbCommand = myDatabase.GetStoredProcCommand("GetApplicationSettings"))
{
using (DataSet myds = myDatabase.ExecuteDataSet(dbCommand))
{
dtVariables = myds.Tables[0].Copy();
}
}
foreach (DataRow myDataRow in dtVariables.Rows)
{
if (myDataRow.Field<string>("AppVarName") == "SettingsCache")
{
cacheDuration = Convert.ToInt32(myDataRow.Field<string>("AppVarValue"));
}
if (cacheDuration == 0)
{
cacheDuration = 300;
}
}
HttpRuntime.Cache.Insert(cacheKey, dtVariables, null, DateTime.Now.AddSeconds(cacheDuration), TimeSpan.Zero);
}
foreach (DataRow myDataRow in dtVariables.Rows)
{
HttpContext.Current.Application[myDataRow.Field<string>("AppVarName")] = myDataRow.Field<string>("AppVarValue");
}
}
}
}
Add a reference in your web app to the Class Library
Create a Global.asax and add update the Application_Start and Application_BeginRequest methods to look like this:
protected void Application_Start(object sender, EventArgs e)
{
Web.AppManager.ApplicationVariableManager AppManager = new Web.AppManager.ApplicationVariableManager();
AppManager.UpdateApplicationVars();}protected void Application_BeginRequest(object sender, EventArgs e)
{
Web.AppManager.ApplicationVariableManager AppManager = new Web.AppManager.ApplicationVariableManager();AppManager.UpdateApplicationVars();
}
Add a check in your Page_Load of your site master's code behind this check:
if (Convert.ToInt32(Application["ApplicationStatus"])==0)
Response.Redirect("/Down.aspx");
Now make a Down.aspx page and make sure your Default.aspx page is using your site master. Voila. The CacheSettings variable allows you to tinker with how long that variables are cached.
All standard disclaimers apply....works on my machine. Let me know if you have questions or can improve it (I am sure there is room there for improvement). Here's the code.
So I'm headed to my first TechEd. I'm really excited. The presentation list is huge! I think I've got them all mapped out I think like 27 in 4 days...crazy. It's almost like a developer pilgrimage. I will also be twittering a lot so check out #TechEd (I think that'll be the group name...I'll update if it's different).
Edit: Updated the twitter tag
So in researching another series I am writing I got a wild desire to do some Linq to SQL performance testing. So I whipped up a database table and populated it with data and went about writing some code. What you'll see in the code is 4 tests one with a fairly standard Linq query and a loop through the results (just to make sure the lazy loading has been done), a Linq query using a sproc (again looping through the results), a fairly standard T-SQL query DataReader which loops through the results and creates a List of objects (to simulate the collections that Linq returns) and a stored procedure using the same query (again looping through the results to simulate a result from Linq). The idea with the 'standard' methods of T-SQL and the Stored Procedure was to return the same slick collections of 'objects' that Linq produces, so to that end I made a small POCO that resembles our result set.
What's interesting is that for larger record sets Linq to Sql slows way down and DataReader speeds up. For smaller record sets the opposite is true.
However I think something shifty is going on because the Linq methods 'feel' faster...meaning I get to the command prompt faster and with the Data Reader there is a pause after it displays the elapsed time.
My database table is fairly simple:
CREATE
TABLE [dbo].[Contact](
[contactid] [int]
IDENTITY(1,1) NOT NULL,
[contactfirstname] [varchar]
(50) NOT NULL,
[contactlastname] [varchar]
(50) NOT NULL,[contactdate] [datetime] NOT NULL
)
ON [PRIMARY]
My Poco class looks like:
public class MyContact
{
private int _contactid;
public int ContactID { get; set; }
private string _contactfirstname;
public string ContactFirstName { get; set; }
private string _contactlastname;
public string ContactLastName { get; set; }
private DateTime _contactdate;
public DateTime ContactDate { get; set; }
public MyContact(int contactid, string contactfirstname, string contactlastname, DateTime contactdate)
{
_contactid = contactid;
_contactfirstname = contactfirstname;
_contactlastname = contactlastname;
_contactdate = contactdate;
}
}
And here is my actual code:
class Program
{
public static string connstring = "Data Source=localhost;Initial Catalog=TestDB;
Persist Security Info=True;User ID=perftest;Password=12345";
private static void UseLinq(int i)
{
using (ContactsDataContext ctx = new ContactsDataContext())
{
var cont = from p in ctx.Contacts
select p;
foreach (Contact c in cont)
{
i = i + 1;
//Console.WriteLine(i.ToString());
}
}
}
private static void UseSPLinq(int i)
{
using (ContactsDataContext ctx = new ContactsDataContext())
{
var cont = from p in ctx.GetContacts()
select p;
foreach (var mycontact in cont)
{
i = i+1;
//Console.WriteLine(i.ToString());
}
}
}
private static void TSQLDataReader(int i)
{
using (SqlConnection conn = new SqlConnection(connstring))
{
conn.Open();
SqlCommand myCommandObject = new SqlCommand();
myCommandObject.CommandType = CommandType.Text;
myCommandObject.CommandText = "select contactid, contactfirstname,
contactlastname, contactdate from contact";
myCommandObject.Connection = conn;
using (SqlDataReader MyDataReader = myCommandObject.ExecuteReader())
{
IList<MyContact> myList = new List<MyContact>();
while (MyDataReader.Read())
{
MyContact mytmpContact = new MyContact(
(int)MyDataReader[0],
MyDataReader[1].ToString(),
MyDataReader[2].ToString(),
(DateTime)MyDataReader[3]
);
myList.Add(mytmpContact);
}
foreach (MyContact mc in myList)
{
i = i + 1;
//Console.WriteLine(i.ToString());
}
}
conn.Close();
myCommandObject.Dispose() ;
}
}
private static void SPDataReader(int i)
{
using (SqlConnection conn = new SqlConnection(connstring))
{
conn.Open();
SqlCommand myCommandObject = new SqlCommand();
myCommandObject.CommandType = CommandType.StoredProcedure;
myCommandObject.CommandText = "GetContacts";
myCommandObject.Connection = conn;
using (SqlDataReader MyDataReader = myCommandObject.ExecuteReader())
{
IList<MyContact> myList = new List<MyContact>();
while (MyDataReader.Read())
{
MyContact mytmpContact = new MyContact(
(int)MyDataReader[0],
MyDataReader[1].ToString(),
MyDataReader[2].ToString(),
(DateTime)MyDataReader[3]
);
myList.Add(mytmpContact);
}
foreach (MyContact mc in myList)
{
i = i + 1;
//Console.WriteLine(i.ToString());
}
}
conn.Close();
myCommandObject.Dispose();
}
}
static void Main(string[] args)
{
int i = 0;
Stopwatch mystopwatch = new Stopwatch();
mystopwatch.Start();
if (args[0] == "StraightLinq")
{
UseLinq(i);
}
if (args[0] == "SPLinq")
{
UseSPLinq(i);
}
if (args[0] == "T-SQL_DataReader")
{
TSQLDataReader(i);
}
if (args[0] == "SP_DataReader")
{
SPDataReader(i);
}
mystopwatch.Stop();
TimeSpan ts = mystopwatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds);
Console.WriteLine(elapsedTime, "RunTime");
}
}
Here's my results for 50,000 records:
|
Avg (5 Runs) |
Speed Difference |
| StraightLinq |
0.5554 |
2.986022 |
| SPLinq |
0.4242 |
2.280645 |
| T-SQL_DataReader |
0.1886 |
1.013978 |
| SP_DataReader |
0.186 |
1 |
Here's my results for 1,000 records:
|
Avg (5 Runs) |
Speed Difference |
| StraightLinq |
0.4984 |
1.372247 |
| SPLinq |
0.3632 |
1 |
| T-SQL_DataReader |
0.954 |
2.626652 |
| SP_DataReader |
0.948 |
2.610132 |
Are these results similar to what you are finding or is there an issue with my testing methodolgy? Drop a comment if you have suggestions on how to improve or correct this test.
Edit: Ugh these layouts are bad for wide data...sorry about that.
Edit: The extra for each loop in the T-Sql and SP versions of the test were the culprit to the higher values in the 1000 record test. With those loops removed the times were about .17
In the previous posts we dropped a table from the Server Explorer onto our design surface and saw how a SqlDataSource was created with T-SQL statements to populate the basic functions of our control. In the second part we replaced our T-SQL with stored procedures and saw how this can help us maintain our code. However this is method still tightly couples our presentation layer to our data layer (what there is of one). While I'm not comfortable saying this is always wrong...it's definately not always right. For RAD (rapid application development), quick prototyping and short lived specialized applications this may be sufficient for you. However if your application is going to be maintained and grow over a longer period of time, you will quickly find that the SqlDataSource will become a sticky issue.
Consider this scenario: You have a dropdown list of some value on your page that you want to populate with data from a SqlDataSource. So you write a stored procedure to return your list. You add the SqlDataSource to your page. Add your dropdown list control and bind it to your datasource and blammo...working page. Two months later a business rule has sprung up that certain values from that list will be excluded based on other conditions within the application. Let's assume for a moment that those "conditions" are unique to the user that is currently logged into your application and are not persisted in the database. Meaning that we cannot use our stored procedure to limit the result set. Now we are forced with overriding the binding behavior of our dropdown list. I personally avoid overriding the behavior of standard controls in all but the most necessary scenarios. In my view it leads to very difficult to maintain code. A 'better' solution to this is to create a business object that controls the data being returned to the application, and then the application doesn't need to do anything fancy it just binds that data to the control.
It all comes down again to seperation of concerns. The Data Access Layer (the database and objects that get data from it) does data access, the Presentation Layer (the html/windows form) presents, and the Business Logic Layer (classes that sit between the Data Access and Presentation layers) makes all the decisions. While this is what is highly desired there are practical reasons that these roles blend from time to time...the key is to limit them whenever possible. I prefer to have all my presentation layer pages (or Windows Form Elements) be as 'dumb' as possible. So in the scenario above the SqlDataSource lets us down in that regard. However, having said that there is a time and place for everything so knowing the basics of how to use them is essential.
Mark Twain said: "To the man with a hammer, everything looks like a nail." Don't let SqlDataSource be your hammer. Likewise remember that it is there and can help you with some tasks.
I'm going to leave the SqlDataSource alone for a while and in the next post I'll start discussing the ObjectDataSource and building some basic classes and show you a couple of fun and easy data access patterns.
More Posts
Next page »