Lance Warthen recommends FullCalendar, a jQuery plugin, for making your own Google Calendaresque calendar.
Tuesday, July 31, 2012
SqlCommand using example
using System.Data.SqlClient;
namespace OurApp.Models
{
public static class HtmlTable
{
public static string Render()
{
string[] model = new string[] { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"" };
string connection_string =
System.Configuration.ConfigurationManager.ConnectionStrings["baseDB"]
.ConnectionString;
using (SqlConnection con = new SqlConnection(connection_string))
{
SqlCommand cmd = new SqlCommand("SELECT * FROM MyData", con);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
if (reader[1].ToString() == "1")
{
model[0] = reader[2].ToString();
model[1] = reader[3].ToString();
model[2] = reader[4].ToString();
model[3] = reader[5].ToString();
model[4] = reader[6].ToString();
model[5] = reader[7].ToString();
}
if (reader[1].ToString() == "2")
{
model[6] = reader[2].ToString();
model[7] = reader[3].ToString();
model[8] = reader[4].ToString();
model[9] = reader[5].ToString();
model[10] = reader[6].ToString();
model[11] = reader[7].ToString();
}
if (reader[1].ToString() == "3")
{
model[12] = reader[2].ToString();
model[13] = reader[3].ToString();
model[14] = reader[4].ToString();
model[15] = reader[5].ToString();
model[16] = reader[6].ToString();
model[17] = reader[7].ToString();
}
}
}
more code follows...
Monday, July 30, 2012
Introduce Middle Man
What is one has two classes where one class inherits from the other like so?
- namespace MiddleMan.Models
{
public class Crow : Animal
{
public Crow()
{
bearsLiveYoung = false;
hasLegs = true;
canFly = true;
lifespan = 1;
}
public int lifespan {
get { return base.lifespan; }
set
{
if (value > 0 && value < 30)
{
base.lifespan = value;
}
}
}
}
} - namespace MiddleMan.Models
{
public class Animal
{
public bool bearsLiveYoung { get; set; }
public bool hasLegs { get; set; }
public bool canFly { get; set; }
public int lifespan { get; set; }
}
}
What if a third class is sandwiched between the two? What might we call this new item? A middle man?
- namespace MiddleMan.Models
{
public class Crow : Bird
{
public Crow()
{
canFly = true;
lifespan = 1;
}
public int lifespan {
get { return base.lifespan; }
set
{
if (value > 0 && value < 30)
{
base.lifespan = value;
}
}
}
}
} - namespace MiddleMan.Models
{
public class Bird : Animal
{
public Bird()
{
bearsLiveYoung = false;
hasLegs = true;
}
}
} - namespace MiddleMan.Models
{
public class Animal
{
public bool bearsLiveYoung { get; set; }
public bool hasLegs { get; set; }
public bool canFly { get; set; }
public int lifespan { get; set; }
}
}
I think that is what Martin Fowler might call it. I think he might dub this sort of refactoring "Introduce Middle Man." I suppose I could be wrong.
Sunday, July 29, 2012
IDisposable
Saturday, July 28, 2012
real-time updates
- Server-side events are push notifications empowered by what is called Comet (a kind of Reverse AJAX) which are streamed to the client from the server. They are intended to empower the client to get, not push back to the server. Piggyback (a kind of Reverse AJAX) allows data to travel back to the server whenever the browser next pings it, perhaps on changing pages, providing one way to hack around the problem of one-way communication.
- Long polling (a kind of real AJAX) offers a connection from the client to the server which is held open.
- Forever Frame involves letting an iframe load in your page and then having JavaScript running within the iframe bubble beyond the iframe itself to bias the page it sits within. Typically such an iframe will be zero pixels wide by zero pixels tall holding no cosmetic function and no role beyond the AJAX hack it empowers. JavaScript processes in your page may force the iframe to load different things at different times. The content that materializes in the iframe, varied as it may be, will always be HTML that is largely empty beyond blobs of JavaScript. JavaScript functions in HTML will run sequentially as they are loaded.
- Web sockets are coming with IIS8! They are bidirectional communication channels for client and server talk. They can fail! One has to have one of the three items above ready to go as a failover when planning to use web sockets.
Addendum 3/29/2013: see this
a "normal" connection string
Data Source=123.45.67.890;Initial Catalog=hexdata;User Id=hexspiral;Password=1stFocus;
Friday, July 27, 2012
grab System.Web.HttpContext.Current.User.Identity.Name at Application_AuthorizeRequest instead of Application_AuthenticateRequest when upgrading a site from IIS6 to IIS7
Wednesday, July 25, 2012
HTML5 data tags, long polling, keyup instead of keypress (which works badly in IE), .toLowerCase(), the live variation of binding for elements created by jQuery after the page loads, wire ups for dropdowns, millisecond calculations for 50 and 15 seconds, messing with dropdown lists, and string splitting are all on exposition in this big, ugly procedural mess!
@using MvcAjax.Core
@model List<MvcAjax.Core.RegisteredDriver>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td valign="top" width="150" align="left" style="font-size: 16px; line-height: 30px;
padding-right: 50px;">
<div>View and reassign Vehicles for Registered Driver.</div>
<div style="padding: 10px 0 10px 0;">Select Registered Driver by LicenseNumber.</div>
<div>
<select id="selector">
<option value="nada" selected></option>
@foreach(RegisteredDriver registeredDriver in Model)
{
<option value="@registeredDriver.RegisteredDriverId"
data-extra='@registeredDriver.IsRequiredToWearGlasses
@registeredDriver.LicenseExpiration'>@registeredDriver.LicenseNumber
</option>
}
</select>
</div>
</td>
<td valign="top" width="601" height="460">
<div id="spinner" style="display: none;"></div>
<div id="datapoints" style="display: block;"></div>
</td>
</tr>
</table>
@Html.Partial("Next", "Eight")
<script type="text/javascript">
$(function () {
setstage();
var selectorValue = $("#selector option:selected").val();
$('#selector').click(function () {
if (selectorValue != $("#selector option:selected").val()) {
selectorValue = $("#selector option:selected").val();
setstage();
}
}).keyup(function (e) {
switch (e.keyCode) {
case 38:
if (selectorValue != $("#selector option:selected").val()) {
selectorValue = $("#selector option:selected").val();
setstage();
}
break;
case 40:
if (selectorValue != $("#selector option:selected").val()) {
selectorValue = $("#selector option:selected").val();
setstage();
}
break;
case 13:
if (selectorValue != $("#selector option:selected").val()) {
selectorValue = $("#selector option:selected").val();
setstage();
}
break;
default:
break;
}
});
$('.reassigner').live('click', function () {
if (selectorValue != $(this).val()) {
selectorValue = $(this).val();
resetstagewhilereassigningvehicle($(this).val(), $(this).data('extra'));
}
}).live('keyup', function (e) {
switch (e.keyCode) {
case 38:
if (selectorValue != $(this).val()) {
selectorValue = $(this).val();
resetstagewhilereassigningvehicle($(this).val(), $(this).data('extra'));
}
break;
case 40:
if (selectorValue != $(this).val()) {
selectorValue = $(this).val();
resetstagewhilereassigningvehicle($(this).val(), $(this).data('extra'));
}
break;
case 13:
if (selectorValue != $(this).val()) {
selectorValue = $(this).val();
resetstagewhilereassigningvehicle($(this).val(), $(this).data('extra'));
}
break;
default:
break;
}
});
});
function setstage() {
var currentDriver = $("#selector option:selected").val();
if (currentDriver == "nada") {
$("#spinner").attr('style', "display: none;");
document.getElementById("datapoints").innerHTML = "";
$("#datapoints").attr('style', "display: block;");
} else {
getajaxcontent();
}
}
function getajaxcontent() {
var currentDriver = $("#selector option:selected").val();
$("#datapoints").attr('style', "display: none;");
document.getElementById("datapoints").innerHTML = "";
$("#spinner").attr('style', "display: block;");
var licenseNumber = $("#selector option:selected").text();
var metaData = $("#selector option:selected").data('extra');
$.ajax({
type: "POST",
url: '/Ajax/GetVehiclesForRegisteredDriver/' + currentDriver,
async: true,
cache: false,
timeout: 50000,
dataType: 'json',
success: function (result) {
var counter = 0;
var vehicledetails = "";
$.each(result, function (index, value) {
vehicledetails = vehicledetails + "<div class='altout'>";
vehicledetails = vehicledetails + "<div class='in'>License Plate</div>";
vehicledetails = vehicledetails + "<div class='in'>Registration Expires</div>";
vehicledetails = vehicledetails + "<div class='in'>Is Insured?</div>";
vehicledetails = vehicledetails + "</div>";
vehicledetails = vehicledetails + "<div class='out'>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Plate + "</div>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Date + "</div>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Bool + "</div>";
vehicledetails = vehicledetails + "</div>";
vehicledetails = vehicledetails + "<div class='out'>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Year + "</div>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Make + "</div>";
vehicledetails = vehicledetails + "<div class='in'>" + value.Model + "</div>";
vehicledetails = vehicledetails + "</div>";
vehicledetails = vehicledetails + "<div class='datapoint'>";
vehicledetails = vehicledetails + "<select class='reassigner' data-extra='" +
value.Guid + "'>";
@foreach(RegisteredDriver registeredDriver in Model)
{
<text>vehicledetails = vehicledetails + "<option
value='@registeredDriver.RegisteredDriverId'";
if ('@registeredDriver.RegisteredDriverId' == currentDriver) {
vehicledetails = vehicledetails + " selected";
}
vehicledetails = vehicledetails +
">@registeredDriver.LicenseNumber</option>";</text>
}
vehicledetails = vehicledetails + "</select>";
vehicledetails = vehicledetails + "</div>";
counter++;
});
if (counter == 0) {
vehicledetails = "<div class='altin'>...owns no Vehicles</div>";
}
var html = "<div class='altout'>";
html = html + "<div class='in'>License Number</div>";
html = html + "<div class='in'>Expiration Date</div>";
html = html + "<div class='in'>Must Wear Glasses?</div>";
html = html + "</div>";
html = html + "<div class='driverspecs'>";
html = html + "<div class='in'>" + licenseNumber + "</div>";
html = html + "<div class='in'>" + metaData.split(' ')[1] + "</div>";
html = html + "<div class='in'>" + metaData.split(' ')[0].toLowerCase() + "</div>";
html = html + "</div>";
html = html + vehicledetails;
$('#datapoints').append(html);
$("#spinner").attr('style', "display: none;");
$("#datapoints").attr('style', "display: block;");
},
error: function (xhr, errorType, exception) {
setTimeout(getajaxcontent(), 15000);
}
});
}
function resetstagewhilereassigningvehicle(driver, vehicle) {
$.ajax({
type: "POST",
url: '/Ajax/UpdateVehicleOwner/' + vehicle + "@@" + driver,
async: true,
cache: false,
timeout: 50000,
dataType: 'json',
success: function (result) {
$("#selector").val(driver);
setstage();
},
error: function (xhr, errorType, exception) {
setTimeout(resetstagewhilereassigningvehicle(driver, vehicle), 15000);
}
});
}
</script>
use HttpContext.Current.User.Identity.Name in C#
In the name of fixing this, I experimented with the web forms code behind below:
using System;
namespace Tinkering
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
System.Security.Principal.IPrincipal user = System.Web.HttpContext.Current.User;
MyControl.Text = "User: " + user.Identity.Name;
}
}
}
HttpContext.Current.User.Identity.Name in C# may just end up null at IIS7 hosting.
But, it doesn't have to be so. I think I've found a fix here and I offer the following eight steps, copied from the bottom of the page, which worked for me.
- Set the DisableStrictNameChecking registry entry to 1. For more information about how to do this, click the following article number to view the article in the Microsoft Knowledge Base: 281308 (http://support.microsoft.com/kb/281308/ ) Connecting to SMB share on a Windows 2000-based computer or a Windows Server 2003-based computer may not work with an alias name
- Click Start, click Run, type regedit, and then click OK.
- In Registry Editor, locate and then click the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
- Right-click MSV1_0, point to New, and then click Multi-String Value.
- Type BackConnectionHostNames, and then press ENTER.
- Right-click BackConnectionHostNames, and then click Modify.
- In the Value data box, type the host name or the host names for the sites that are on the local computer, and then click OK.
- Quit Registry Editor, and then restart the IISAdmin service.
Tuesday, July 24, 2012
set up the ASP.NET framework for IIS7 in your local environment
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i
...running this at the command line as administrator will alleviate errors like this...
Handler "PageHandlerFactory-Integrated" has a bad module "ManagedPipelineHandler" in its module list
I got this from here.
dynamically make ASP.NET web forms controls
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master"
AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApp._Default"
%>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:Panel runat="server" ID="OuterContainer">
<asp:CheckBox ID="FooBox" runat="server" Text="Foo" />
</asp:Panel>
</asp:Content>
The preceeding web form has the following code behind.
using System;
using System.Web.UI.WebControls;
namespace WebApp
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
CheckBox barCheckBox = new CheckBox();
barCheckBox.Text = "Bar";
barCheckBox.ID = "BarBox";
OuterContainer.Controls.Add(barCheckBox);
}
}
}
As you can see the FooBox is made in the web form itself while the BarBox is made in the code behind. The result looks like so:
I have heard that Page_Init is the really the place to make dynamic controls. I was able to refactor my code behind to be as such:
using System;
using System.Web.UI.WebControls;
namespace WebApp
{
public partial class _Default : System.Web.UI.Page
{
private CheckBox barCheckBox;
protected void Page_Init(object sender, EventArgs e)
{
barCheckBox = new CheckBox();
barCheckBox.Text = "Bar";
barCheckBox.ID = "BarBox";
}
protected void Page_Load(object sender, EventArgs e)
{
OuterContainer.Controls.Add(barCheckBox);
}
}
}
Whatever.
Monday, July 23, 2012
readonly C# keyword
REALLY update a Web.config file to be prepped for IIS7 instead of IIS6
This is bad. Instead use this command:
appcmd migrate config mylocalarecord/
The site must be set up in IIS and you must have the trailing slash. Run the command prompt as administrator.
associate TFS to a project
- With the Solution Explorer open in Visual Studio 2010...
- Go to: File > Source Control > Change Source Control...
- If you don't see the Change Source Control... option, click on the Solution Explorer and try again.
Sunday, July 22, 2012
XML, XPath, XSD, and XSLT
The last two chapters I've read in C# 4.0 in a Nutshell have been all about XML (Extensible Markup Language). Man, I am so sick of reading about XML. Luckily, I just finished chapter 11 and now I am done with it. Here are some the things I learned of in this chapter:
- XPath is described like so verbatim at bottom of page 466: XPath is the W3C standard for XML querying. In the .NET Framework, XPath can query an XmlDocument rather like LINQ queries an X-DOM. XPath has a wider scope, though, in that it's also used by other XML technologies, such as XML schema, XLST, and XAML.
- XSD stands for XML Schema Definition and is written in XML itself, typically saved to a .xsd document, which defines how another patch of XML should be shaped. An XSD can have an XSD in the same way a derivative can have a derivative in calculus. A XSD may be consumed to validate that a swath of XML is of the appropriate format, holding expected nodes, etc.
- XSLT stands for Extensible Stylesheet Language Transformations and like XSD is itself XML while also being metadata for XML while typically saved out to documents with a different extension (.xslt). Use an XSLT to provide a spec for transforming one variety of XML into another. One might use an XSLT document to transform an RSS (RDF (Resource Description Framework, a W3C metadata spec) Site Summary or Really Simple Syndication) document into an XHTML (Extensible Hypertext Markup Language) document.
Saturday, July 21, 2012
progressively load records with AJAX!
<h4>AJAJ-style "AJAX" in progressive record loading:</h4>
<div id="records">
<div class='altout'>
<div class='in'>License Number</div>
<div class='in'>Expiration Date</div>
<div class='in'>Must Wear Glasses?</div>
</div>
</div>
@Html.Partial("Next", "Three")
<script type="text/javascript">
$(function () {
var collection = new Array();
collection.length = @ViewBag.OneThirdOfRecordCount;
var counter = 0;
$(collection).each(function () {
$('#records').animate({ opacity: "1" }, 100, function () {
$.ajax({
type: "POST",
url: '/Ajax/GetPageOfRegisteredDriverRecordSet/' + counter,
dataType: 'json',
success: function (result) {
$.each(result, function(index, value) {
var html = "<div class='out'>";
html = html + "<div class='in'>" + value.Id + "</div>";
html = html + "<div class='in'>" + value.Date + "</div>";
html = html + "<div class='in'>" + value.Bool + "</div>";
html = html + "</div>";
$('#records').append(html);
});
}
});
counter = counter + 3;
});
});
});
</script>
@Html.Partial("Next", "Three") does not apply to the example. It was just in my code. Don't let it trip you up. Also, I have the loop getting three records at time. The three is hard coded into the C# behind /Ajax/GetPageOfRegisteredDriverRecordSet/ and thus cannot be seen. I should probably pass it in somehow, huh?
Friday, July 20, 2012
Ordering and Pagination in fluent NHibernate!
startingPosition and maximumQuantity below are int variables.
IList iList = session.CreateCriteria(typeof(RegisteredDriver)).AddOrder(Order.Asc("LicenseNumber")).SetFirstResult(startingPosition).SetMaxResults(maximumQuantity).List();
slowly count to twenty while appending the numbers to a div with jQuery
<div id="records">
</div>
<script type="text/javascript">
$(function () {
var collection = new Array();
collection.length = 20;
var counter = 0;
$(collection).each(function () {
$('#records').animate({ opacity: "1" }, 1000, function () {
counter++;
$('#records').append(counter + '<br />');
});
});
});
</script>
Note that opacity: "1" does nothing. It is just a placeholder.
Partial versus RenderPartial
@Html.Partial("Whatever", myModel)
...is going to work standalone in a view while...
Html.RenderPartial("Whatever", myModel)
...is going to work inside of a code loop as shown here, as best as I can tell.
coupling, complexity, and cohesion
Thursday, July 19, 2012
button type="submit" ...will allow a button to submit a form in lieu of being something that just sits there and does nothing unless you wire up JavaScript/jQuery to bind to its click event.
http://www.w3schools.com/tags/tag_button.asp & http://www.w3schools.com/tags/att_button_type.asp touch on how the button type works. The reset type will clear the form fields!
Scott Bellware on kick-starting projects
At Lean Software Austin's Monday night meeting I saw Scott Bellware speak on kick starting projects. He suggested a "Just Ship It" mentality is not the way to go and offered Kaizen as a better goal of which http://en.wikipedia.org/wiki/Kaizen asserts: Kaizen, Japanese for "improvement", or "change for the better" refers to philosophy or practices that focus upon continuous improvement of processes in manufacturing, engineering, and business management. It has been applied in healthcare, psychotherapy, life-coaching, government, banking, and other industries. When used in the business sense and applied to the workplace, kaizen refers to activities that continually improve all functions, and involves all employees from the CEO to the assembly line workers.
The obvious difference between the two is that one should care about everything instead of just "the bigger priorities" and the subtext here is that one should not take shortcuts. Mr. Bellware drew a few large circles on a whiteboard to represent "the bigger priorities" and then a smattering of little circles to represent all the shortcuts one might take. He then drew connections between the small circles and offered that their whole would add up a problem bigger than any of the "the bigger priorites." Shortcuts are defined as anything you do that you know that you shouldn't be doing in the name of "Just Ship It." They are conscious errors. Shortcuts are counter to the Lean approach of not letting something progress forward if it is flawed.
That was the whole talk, and yet, not unlike the last event I went to at Lean Software Austin, we went off a few tangents. Interesting things mentioned include:
- This spectrum was whiteboarded. In Lean one focuses towards the top of this spectrum and the bottom is.... bad.
^
|
|
|
|
|
|
|
|
|
v
abstract
principles
practices
tools and patterns
concrete
- Cargo-culting is the art of using technologies or patterns because you heard someone else suggest them to be good for fix. This goes at the bottom of the spectrum above and was heavily badmouthed. Cargo-culting is not the same as shortcutting as I am not necessarily making a conscious mistake when cargo-culting. The bad mouthing of cargo-culting and the suggestion that any pre-packaged approach is counter to an improving culture in which one is trying to be better in the name of chasing Kaizen was depressing. It made me think of Socrates' assertion that everyone learns in an Introduction to Philosophy college class that "all that you can know is that you can know nothing." It feels like our pursuit of knowledge as software professionals is Sisyphean.
- Toyota! I end with one of my old notes from seeing Paul Rayner speak at No Fluff Just Stuff a year ago: The Lean Timeline featuring Taichi Ohno (and yes Henry Ford and Frederick Taylor)
Wednesday, July 18, 2012
What does... compilation debug="true" ...do in Web.config?
Sunday, July 15, 2012
generate a random number with C#
The second line below assigns a number between 0 and 219134.
Random random = new Random();
int randomNumber = random.Next(219135);
getting a row count in fluent NHibernate and fighting through a few errors
var count = session.CreateCriteria(typeof (RegisteredDriver))
.SetProjection(Projections.RowCount());
int conversion = Convert.ToInt16(count);
I tried the approach above with fluent NHibernate to try to get a row count and got this error message:
Unable to cast object of type 'NHibernate.Impl.CriteriaImpl' to type 'System.IConvertible'.
I refactored to this:
var count = session.CreateCriteria(typeof (RegisteredDriver))
.SetProjection(Projections.RowCount()).FutureValue<Int32>();
int conversion = count.Value;
I started to get this error:
Interceptor.OnPrepareStatement(SqlString) returned null or empty SqlString.
I read a thread online which suggested that my map was messed up. I looked into it the issue and realized I just didn't have a map. Duh. I made one.
using FluentNHibernate.Mapping;
using MvcAjax.Core;
namespace MvcAjax.Infrastructure
{
public class RegisteredDriverMap : ClassMap<RegisteredDriver>
{
public RegisteredDriverMap()
{
Id(x => x.RegisteredDriverId);
Map(x => x.LicenseNumber);
Map(x => x.LicenseExpiration);
Map(x => x.IsRequiredToWearGlasses);
}
}
}
I kept getting the following as an error even after adding the map:
No persister for: MvcAjax.Core.RegisteredDriver
I found I was looking into the wrong place in my repository for the map.
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure().Database(
MsSqlConfiguration.MsSql2005
.ConnectionString(c => c
.FromAppSetting("FluentNHibernateConnection"))
)
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<RegisteredDriver>())
.BuildSessionFactory();
}
I changed the reference to RegisteredDriver above to be to RegisteredDriverRepository which sat in the same namespace as RegisteredDriverMap. This fixed things.
Saturday, July 14, 2012
make a foreign key in MSSQL
This helped me some with this:
BEGIN TRANSACTION
GO
CREATE TABLE dbo.RegisteredDriver
(
RegisteredDriverId uniqueidentifier NOT NULL,
LicenseNumber int NOT NULL,
LicenseExpiration datetime NOT NULL,
IsRequiredToWearGlasses bit NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.RegisteredDriver ADD CONSTRAINT
PK_RegisteredDriver PRIMARY KEY CLUSTERED
(
RegisteredDriverId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE TABLE dbo.Vehicle
(
VehicleId uniqueidentifier NOT NULL,
RegisteredDriverId uniqueidentifier NOT NULL,
YearMakeModel varchar(50) NOT NULL,
LicensePlate nchar(7) NOT NULL,
RegistrationExpiration datetime NOT NULL,
IsInsured bit NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Vehicle ADD CONSTRAINT
PK_Vehicle PRIMARY KEY CLUSTERED
(
VehicleId
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE dbo.Vehicle ADD CONSTRAINT
FK_Vehicle_Vehicle1 FOREIGN KEY
(
VehicleId
) REFERENCES dbo.Vehicle
(
VehicleId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.Vehicle ADD CONSTRAINT
FK_Vehicle_RegisteredDriver FOREIGN KEY
(
RegisteredDriverId
) REFERENCES dbo.RegisteredDriver
(
RegisteredDriverId
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
COMMIT
Friday, July 13, 2012
LINQ to XML
using let in LINQ query syntax
The following test passes and shows off something else I learned from C# 4.0 in a Nutshell.
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LetExample.Tests
{
[TestClass]
public class LetTest
{
[TestMethod]
public void TestLet()
{
int[] digits = {1, 2, 3, 4, 5, 6, 7};
IEnumerable<int> query = from d in digits
let splits = (d%2)
where splits.Equals(0)
select d;
Assert.AreEqual(query.Count(), 3);
Assert.AreEqual(query.ElementAt(0), 2);
Assert.AreEqual(query.ElementAt(1), 4);
Assert.AreEqual(query.ElementAt(2), 6);
}
}
}
Wednesday, July 11, 2012
CQRS Triangle
I saw Gabriel Schenker of Topaz Technologies present on CQRS (Command Query Responsibility Segregation), a term coined by Greg Young, at ADNUG on Monday.
- In Gabriel's approach there is a triangle. The client hands commands to the domain which hands events to the read model. The client also hands queries to the read model for data and from the read model gets back DTOs.
- Commands handed to the domain can spawn events which are pushed to the read model, or they may simply fail. The client will get back an Ack/Nack (pass or fail) for each command handed to the domain. An Ack/Nack is not just a boolean value as the Nack carries with it an explanation of what went wrong. Imagine a form being submitted and a success or failure message then bubbling back up to the view.
- The scheduling of a new task, such as placing an order an amazon.com, is an example of a typical command. Eventual consistency will come from the triangle!
0.1% of the time when amazon.com turns out to be out of copies of "How to Win Friends and Influence People" upon attempted event fulfillment even though copies existed upon ordering, the asynchronous inconsistency is addressed with an apology and "store credit." This approach comes with a "it is OK to occasionally fail" philosophy. - An event spool should hang off of the domain, saving the events somehow. (This sort of logging is called "Event Sourcing.") Gabriel's approach did eventhing with flat files. One should be able to recreate everything in the read model (again, flat files in Gabriel's implementation) from rerunning events in the appropriate order.
- The events in the event spool should not be editable. They should go in in a sequence so that they may be rerun in the appropriate sequence.
- What should we do if an event is messed up and we cannot edit it? We should write a correction to compensate for the error instead of trying to pretend the error didn't exist (via redacting) in the same way accountants write "journal corrections."
- Aggregrates (in the domain) process POCO commands pushed from the client. If things go right, an aggregrate will produce one event per command.
Will Hendrix's AOPesque means for redirecting users without appropriate permissions from accessing forbidden content at Global.asax
protected override void Application_AuthenticateRequest (object sender, EventArgs e)
{
var context = HttpContext.Current;
var currentNode = SiteMap.CurrentNode;
var resourceType = Path.GetExtension(context.Request.Url.AbsolutePath).Replace(".",
string.Empty).ToLower();
if (resourceType == "aspx" && currentNode != null && context.User != null)
{
if (currentNode.Roles.Count == 1 && (string.Compare("*",
currentNode.Roles[0].ToString().Trim()) == 0))
return;
if (!currentNode.Roles.Cast<string>().Any(role => context.User.IsInRole(role)))
FormsAuthentication.RedirectToLoginPage();
}
}
Tuesday, July 10, 2012
System.Xml.Linq
System.Xml in an application I'm touching ported up to C# 4.0 and from C# 2.0 does not include System.Xml.Linq. When I try to destroy and replace the reference to System.Xml in the Solution Explorer the stuff under the ".NET" tab in the "Add Reference" dialog box is just 2.0 stuff again. I get a .dll from:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll
C:\Windows\Microsoft.NET\Framework\v4.0.30319
...is the better place to fo fishing.
Now I'm done fishing. It turns out...
- The project in question was still on the 2.0 framework. I fixed this by right-clicking on the project in the Solution Explorer and selecting: "Properties"
- System.Xml.Linq turns out to be its own namespace beyond System.Xml
AppDomain.CurrentDomain.BaseDirectory is a good point to start from when fishing for files by file names.
XmlDocument xmlFromSiteMap = new XmlDocument();
xmlFromSiteMap.Load(AppDomain.CurrentDomain.BaseDirectory + "Web.sitemap");
Monday, July 9, 2012
create one cell out of many cells in Excel
This touches on how to merge a bunch of cells into one cell in Excel. At the Home tab, there will be a dropdown for "Merge & Center."
of Luis Blanco and MongoDB
I was comparing notes on MongoDB with Luis Blanco this morning and he told me that he used this driver and suggested that with another driver I might not have the ObjectId external dependency problem I experienced this weekend. He also suggested that Mongo may have a custom Guid implementation in the name of making things easier to find in Mongo.
Sunday, July 8, 2012
Wrapping repositories, MongoDB, and stubbing are all touched on in this blog posting.
UserAccount foo = new UserAccount("tomjaeschke@tomjaeschke.com", "foo");
UserAccount bar = new UserAccount("fevercheese@gmail.com", "bar");
IUserAccountRepository repository =
ObjectFactory.GetInstance<IUserAccountRepository>();
if (repository.GetCountOfExistingUserAccounts() == 0) foo.Status =
UserStatus.Administrative;
repository.SaveNewUserAccount(foo);
repository.SaveNewUserAccount(bar);
I was able to replace the six lines above (which YES sit in the user interface layer of a Palermo Onion which YES is also the bootstrapper for Inversion of Control via Jeremy Miller's StructureMap) with the five lines below.
UserAccount foo = new UserAccount("tomjaeschke@tomjaeschke.com", "foo");
UserAccount bar = new UserAccount("fevercheese@gmail.com", "bar");
UserAccountRepositoryWrapper repository = new
UserAccountRepositoryWrapper(
ObjectFactory.GetInstance<IUserAccountRepository>());
repository.SaveNewUserAccount(foo);
repository.SaveNewUserAccount(bar);
That is all of the user interface code I am going to show in this blog posting! I was able to push the decision making as to whether or not to make a UserAccount administrative down into the Core by creating a repository wrapper like so:
using MongoLogin.Core.Interfaces.DataIntegration;
namespace MongoLogin.Core.InfrastructureWrappers.DataIntegration
{
public class UserAccountRepositoryWrapper
{
public IUserAccountRepository Repository { get; set; }
public UserAccountRepositoryWrapper(IUserAccountRepository repository)
{
Repository = repository;
}
public void SaveNewUserAccount(UserAccount userAccount)
{
int numberOfUsers = Repository.GetCountOfExistingUserAccounts();
if (numberOfUsers == 0) userAccount.Status = UserStatus.Administrative;
Repository.SaveNewUserAccount(userAccount);
}
}
}
The decision making may then be TESTED as part of the Core logic. It is not something dirty and random in the UI.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MongoLogin.Core.InfrastructureWrappers.DataIntegration;
using MongoLogin.Core.Tests.InterfaceTestingHelpers.DataIntegration;
namespace MongoLogin.Core.Tests.InfrastructureWrapperTests.DataIntegration
{
[TestClass]
public class UserAccountRepositoryWrapperTests
{
[TestMethod]
public void SaveNewUserAccountUpdatesUserStatusWhenAppropriateTest()
{
UserAccount userAccount = new UserAccount("tomjaeschke@tomjaeschke.com",
"foo");
StubbedUserAccountRepository stub = new StubbedUserAccountRepository();
stub.ValueToReturnForGetCountOfExistingUserAccounts = 0;
UserAccountRepositoryWrapper repository = new
UserAccountRepositoryWrapper(stub);
repository.SaveNewUserAccount(userAccount);
Assert.AreEqual(stub.UserAccountHandedIntoSaveNewUserAccount.Status,
UserStatus.Administrative);
userAccount = new UserAccount("fevercheese@gmail.com", "bar");
stub = new StubbedUserAccountRepository();
stub.ValueToReturnForGetCountOfExistingUserAccounts = 1;
repository = new UserAccountRepositoryWrapper(stub);
repository.SaveNewUserAccount(userAccount);
Assert.AreEqual(stub.UserAccountHandedIntoSaveNewUserAccount.Status,
UserStatus.Awaiting);
}
}
}
There should be no need to mock methods in the Core assuming the Core is clean from the "contamination" of external dependencies. Instead, the repository that inherits from IUserAccountRepository may be faked by a stub that in turn has no external dependencies.
using MongoLogin.Core.Interfaces.DataIntegration;
namespace MongoLogin.Core.Tests.InterfaceTestingHelpers.DataIntegration
{
public class StubbedUserAccountRepository : IUserAccountRepository
{
public int ValueToReturnForGetCountOfExistingUserAccounts { get; set; }
public UserAccount UserAccountHandedIntoSaveNewUserAccount { get; set; }
public int GetCountOfExistingUserAccounts()
{
return ValueToReturnForGetCountOfExistingUserAccounts;
}
public void SaveNewUserAccount(UserAccount userAccount)
{
UserAccountHandedIntoSaveNewUserAccount = userAccount;
}
}
}
The real repository in the Infrastructure project looks like so:
using MongoDB.Driver;
using MongoLogin.Core;
using MongoLogin.Core.Interfaces.DataIntegration;
using MongoDB.Driver.Builders;
namespace MongoLogin.Infrastructure.DataIntegration
{
public class UserAccountRepository : IUserAccountRepository
{
private string connectionString = "mongodb://localhost/?safe=true";
private string regularExpressionForStatusMatching = "/^([A].*)$/";
private string databaseName = "test";
private string collectionName = "entities";
public int GetCountOfExistingUserAccounts()
{
var server = MongoServer.Create(connectionString);
var database = server.GetDatabase(databaseName);
var allRecords = database.GetCollection<UserAccountDataTransferObject>
(collectionName);
var query = Query.Matches(CommonMagicStrings.Status(),
regularExpressionForStatusMatching);
var legitimateResults = allRecords.Find(query);
return (int)legitimateResults.Count();
}
public void SaveNewUserAccount(UserAccount userAccount)
{
var server = MongoServer.Create(connectionString);
var database = server.GetDatabase(databaseName);
var allRecords = database.GetCollection<UserAccountDataTransferObject>
(collectionName);
var dto = new UserAccountDataTransferObject
{
Email = userAccount.Email,
Password = userAccount.Password,
Status = userAccount.Status.ToString()
};
allRecords.Insert(dto);
allRecords.Save(dto);
}
}
}
The interface that the real and fake repositories inherit from...
namespace MongoLogin.Core.Interfaces.DataIntegration
{
public interface IUserAccountRepository
{
int GetCountOfExistingUserAccounts();
void SaveNewUserAccount(UserAccount userAccount);
}
}
Alright. Whew! Let's slow down for a minute. What am I trying to do? I thought I'd start teaching myself how to interface with MongoDB from C# this weekend and to that end I thought I'd write a super simple login control that lets users create accounts. Administrative users can make awaiting users active. I thought it would be nice if the first user put into the system was conveniently set up to be administrative, but clearly I would not want to do this for most users. I needed to make a repository check to see if any users existed yet to determine if I should save a new user in administrative status. This means I needed to tie the counting of existing users to the saving of users. The dilemma of how to get the logic driving the conditional administrative state into the Core and not the Infrastructure nor the UI in the name of making the testable Core as fat as possible is addressed with my repository wrapper solution. That said, I have a few other things to say. I left this out of the Core and in the Infrastructure layer:
using MongoDB.Bson;
namespace MongoLogin.Infrastructure.DataIntegration
{
public class UserAccountDataTransferObject
{
public ObjectId Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Status { get; set; }
}
}
It turns out that objects put into Mongo have to have an Id of type ObjectId which comes from the MongoDB.Bson namespace which has no business bleeding into the Core and thus all Mongo-hydratable objects cannot live in the Core. Yuck!
That means my Mongo POCO has to have sister in the Core:
namespace MongoLogin.Core
{
public class UserAccount
{
public string Email { get; set; }
public string Password { get; set; }
public UserStatus Status { get; set; }
public UserAccount(string email, string password)
{
PrepareUserAccount(email, password, null);
}
public UserAccount(string email, string password, string status)
{
PrepareUserAccount(email, password, status);
}
public UserAccount(string email, string password, UserStatus status)
{
PrepareUserAccount(email, password, status.ToString());
}
public void PrepareUserAccount(string email, string password, string status)
{
Email = email;
Password = password;
Status = UserStatus.Awaiting;
if (status == UserStatus.Aborted.ToString()) Status = UserStatus.Aborted;
if (status == UserStatus.Active.ToString()) Status = UserStatus.Active;
if (status == UserStatus.Administrative.ToString()) Status =
UserStatus.Administrative;
}
}
}
Also, I'm going to have a bunch of magic strings to manage in working with Mongo it seems. I wrote a kludgy class to start wrangling such.
namespace MongoLogin.Core
{
public static class CommonMagicStrings
{
public static string Email()
{
return "Email";
}
public static string Metadata()
{
return "Metadata";
}
public static string Password()
{
return "Password";
}
public static string Status()
{
return "Status";
}
}
}
I end with the enum.
namespace MongoLogin.Core
{
public enum UserStatus
{
Aborted,
Active,
Administrative,
Awaiting
}
}
db["entities"].find(); will find everything in a Mongo collection while db["entities"].remove(); will drop everything from the collection.
If db["entities"].find(); has stuff db.things.find(); will not retrieve the stuff. Be warned! This confused me.
Saturday, July 7, 2012
Friday, July 6, 2012
OLTP and OLAP
I found this image here and learned that the divide between OLTP (On-line Transaction Processing) of CRUD and OLAP (On-line Analytical Processing) of complex queries is not unlike the difference between a normalized schema and a second denormalized Business Intelligence schema of the same information for reporting respectively.
aligning divs, form fields, and text with CSS
<div style='width:400px; height:40px;'>
<div style='width:198px; height:40px; float: left;'>
<input value='foo' style='width:100px; float:right; text-align: right;' />
</div>
<div style='width:198px; height:40px; float: right;'>
<input value='bar' style='width:100px; float:left;' />
</div>
</div>
<div style='width:400px; height:40px; text-align: center;'>
<input value='baz' style='width:100px; text-align: center;' />
</div>
...makes...
Thursday, July 5, 2012
SelectMany example
This is more magic from C# 4.0 in a Nutshell. This test passes:
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LinqStuff.Tests
{
[TestClass]
public class LinqTests
{
[TestMethod]
public void SelectManyTest()
{
int[] one = new int[] {1, 77, 8, 35};
int[] two = new int[] {2, 300};
int[] three = null;
int[] four = new int[] {4, 44, 55};
int[][] five = new int[][] {one, two, three, four};
List<int> query = five.SelectMany(x => x ?? new int[]{}).ToList();
Assert.AreEqual(query.Count(), 9);
Assert.AreEqual(query[0], 1);
Assert.AreEqual(query[1], 77);
Assert.AreEqual(query[2], 8);
Assert.AreEqual(query[3], 35);
Assert.AreEqual(query[4], 2);
Assert.AreEqual(query[5], 300);
Assert.AreEqual(query[6], 4);
Assert.AreEqual(query[7], 44);
Assert.AreEqual(query[8], 55);
}
}
}
SSIS notes
- SSIS (SQL Server Integration Services) is an ETL (Extract, transform, load).
- To create a new project, open BIDS (SQL Server Business Intelligence Development Studio) and pick "New Project..." from the "File" menu. Pick "Integration Services Connection Project" from the "Business Intelligence Projects." You will be taken through a setup wizard.
- One may pull data from various sources, mess with it, and then put the transformed data to a database.
- Projects have .dtsx extensions.
- The ETL process may be kicked off by way of opening a folder and double-clicking a .dtsx, from within SQL Server, or by way of Windows Task Scheduler.
- One may wrap multiple process step in transactions by way of "containers" with green and red outs.
- One may create record sets based on the Deltas of two tables.
- Control Flows orcestrate the overall steps while Data Flows orcestrate the row-by-row processing at a given step.
impersonate
Having <identity impersonate="true"/> in a system.web portion of the Web.config can make a web site masquerade as your active directory account when attempting to connect to a database, overriding other settings to the contray. Beware. This can happen in IIS7 too. I have found that not everything in system.web has to be migrated to system.webServer for IIS7 to "see" it. Instead much may remain in system.web and be seen by IIS7.
Tuesday, July 3, 2012
What is a .pdb file?
It is for debugging. If you publish an app to local folder with the Release build definition instead of the Debug build definition you probably won’t have .pdb files unless you’ve gone out of your way to put them in the Release definition. (Go to Build > Configuration Manager... in Visual Studio 2010 to see all of the build definitions.) One of the annoying/helpful things a .pdb will do is it will make references to where files were compiled from bubble up in error messages. Deleting a .pdb will drop this bit of metadata. For every project outside of your UI for which you will publish a .dll, you may have a .pdb as a companion to the .dll (these files sit in the bin folder), if you want one.
Monday, July 2, 2012
automatically update a Web.config file to be prepped for IIS7 instead of IIS6
C:\Windows\System32\inetsrv\Appcmd migrate config C:\Whatever\MyApp\
...is a command line command that should update the Web.config in the second directory to jive with IIS7 per this. All of the magic for IIS6 in the </system.web> tag should be crawled and rewritten as magic for IIS7 in the </system.webServer> tag. I don't think it will work if you do not have IIS7 installed.
Paint.Net is the free Photoshop???
Sunday, July 1, 2012
add something and get it back in Mongo, real easy
j = {name: "mongo"};
db.things.save(j);
db.things.find();
...should bring back to you something like this...
{ "_id" : ObjectId("4fee19344b6e18012aa6c5bf6"), ""name" : "mongo" }
The Boolean adjective comes from George Boole.
George Boole in the 19th century came up with Boolean Logic of which Wikipedia says: "The operations are usually taken to be conjunction, disjunction, and negation, with constants 0 and 1." This means that Boolean as an adjective may have a true/false connotation or an and/or/not connotation. In C# clearly there is a true/false nature to a Boolean variable. When I started doing C#, after having done 3D Studio Max in a former life a dozen years ago, this definition of Boolean confused me because 3D Studio Max uses an and/or/not definition for "Boolean."
In 3D Studio Max one could overlay two independent meshes and then make a new mesh out of the two operands. The common area between the two operands could be kept while the uncommon areas were dropped creating a shape the shape of the space where the two items overlaid, or the two operands could be welded into one whole dropping nothing, or one operand would disappear while subtracting the area it had in common with its partner from its partner not unlike taking a bite out of an apple.
In reading about LINQ in C# 4.0 in a Nutshell I have learned how to do the 3D Studio Max style operations to two "overlaying" collections!
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LinqStuff.Tests
{
[TestClass]
public class LinqTests
{
public LinqTests()
{
}
[TestMethod]
public void AndTest()
{
int[] firstFiveDigits = new int[] { 1, 2, 3, 4, 5 };
int[] fourDigitsAfterThree = new int[] { 4, 5, 6, 7 };
int[] intersect = firstFiveDigits.Intersect(fourDigitsAfterThree).ToArray();
Assert.AreEqual(intersect.Length, 2);
Assert.AreEqual(intersect[0], 4);
Assert.AreEqual(intersect[1], 5);
}
[TestMethod]
public void OrTest()
{
int[] firstFiveDigits = new int[] { 1, 2, 3, 4, 5 };
int[] fourDigitsAfterThree = new int[] { 4, 5, 6, 7 };
int[] union = firstFiveDigits.Union(fourDigitsAfterThree).ToArray();
Assert.AreEqual(union.Length, 7);
Assert.AreEqual(union[0], 1);
Assert.AreEqual(union[1], 2);
Assert.AreEqual(union[2], 3);
Assert.AreEqual(union[3], 4);
Assert.AreEqual(union[4], 5);
Assert.AreEqual(union[5], 6);
Assert.AreEqual(union[6], 7);
}
[TestMethod]
public void NotTest()
{
int[] firstFiveDigits = new int[] { 1, 2, 3, 4, 5 };
int[] fourDigitsAfterThree = new int[] { 4, 5, 6, 7 };
int[] except = firstFiveDigits.Except(fourDigitsAfterThree).ToArray();
Assert.AreEqual(except.Length, 3);
Assert.AreEqual(except[0], 1);
Assert.AreEqual(except[1], 2);
Assert.AreEqual(except[2], 3);
}
}
}
LINQ Join, LINQ Distinct, LINQ Union, overriding equality on C# Structs, and lookups are all jammed into this fat blog posting!
I've been reading about LINQ in "C# 4.0 in a Nutshell" by Joseph Albahari and Ben Albahari! It may be more efficient to do a Join instead of a Select when working against local queries. In doing a joinesque selection, a Select will iterate through an outer collection while iterating through an inner collection over and over again one time for each item in the outer collection. A Join puts the inner collection into a lookup which has a dictionaryesque grab-item-by-key flavor to it allowing for pertinent inner objects to be found without crawling through all of their kin in a collection. The four tests which follow show off joining and all pass.
using System;
using System.Linq;
using System.Collections.Generic;
using LinqStuff.Core;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LinqStuff.Tests
{
[TestClass]
public class LinqTests
{
public LinqTests()
{
}
[TestMethod]
public void JoinTest()
{
Tuple<IEnumerable<RegisteredDriver>, IEnumerable<Car>> tuple =
GetDriversAndCars();
IEnumerable<RegisteredDriver> drivers = tuple.Item1;
IEnumerable<Car> cars = tuple.Item2;
List<RegisteredDriver> list = drivers.Join(
cars,
d => d.LicenseNumber,
c => c.OwnersLicenseNumber,
(d,c) => new RegisteredDriver()
{
LicenseNumber = d.LicenseNumber,
LicenseExpiration = d.LicenseExpiration,
IsRequiredToWearGlasses = d.IsRequiredToWearGlasses,
Cars = new List<Car>(){c}
}
).ToList();
Assert.AreEqual(list.Count, 4);
Assert.AreEqual(list[0].LicenseNumber, 33489432);
Assert.AreEqual(list[0].Cars.Count(), 1);
Assert.AreEqual(list[0].Cars.ElementAt(0).LicensePlate, "DIE HOT");
Assert.AreEqual(list[1].LicenseNumber, 90343534);
Assert.AreEqual(list[1].Cars.Count(), 1);
Assert.AreEqual(list[1].Cars.ElementAt(0).LicensePlate, "888 BAR");
Assert.AreEqual(list[2].LicenseNumber, 90343534);
Assert.AreEqual(list[2].Cars.Count(), 1);
Assert.AreEqual(list[2].Cars.ElementAt(0).LicensePlate, "FOO 42X");
Assert.AreEqual(list[3].LicenseNumber, 54234989);
Assert.AreEqual(list[3].Cars.Count(), 1);
Assert.AreEqual(list[3].Cars.ElementAt(0).LicensePlate, "ABC 123");
}
[TestMethod]
public void JoinTestWithDistinct()
{
Tuple<IEnumerable<RegisteredDriver>, IEnumerable<Car>> tuple =
GetDriversAndCars();
IEnumerable<RegisteredDriver> drivers = tuple.Item1;
IEnumerable<Car> cars = tuple.Item2;
List<RegisteredDriver> list = drivers.Join(
cars,
d => d.LicenseNumber,
c => c.OwnersLicenseNumber,
(d,c) => new RegisteredDriver()
{
LicenseNumber = d.LicenseNumber,
LicenseExpiration = d.LicenseExpiration,
IsRequiredToWearGlasses = d.IsRequiredToWearGlasses,
Cars = cars.Where(x => x.OwnersLicenseNumber ==
d.LicenseNumber).ToList()
}
).Distinct().ToList();
Assert.AreEqual(list.Count, 3);
Assert.AreEqual(list[0].LicenseNumber, 33489432);
Assert.AreEqual(list[0].Cars.Count(), 1);
Assert.AreEqual(list[0].Cars.ElementAt(0).LicensePlate, "DIE HOT");
Assert.AreEqual(list[1].LicenseNumber, 90343534);
Assert.AreEqual(list[1].Cars.Count(), 2);
Assert.AreEqual(list[1].Cars.ElementAt(0).LicensePlate, "888 BAR");
Assert.AreEqual(list[1].Cars.ElementAt(1).LicensePlate, "FOO 42X");
Assert.AreEqual(list[2].LicenseNumber, 54234989);
Assert.AreEqual(list[2].Cars.Count(), 1);
Assert.AreEqual(list[2].Cars.ElementAt(0).LicensePlate, "ABC 123");
}
[TestMethod]
public void JoinTestWithUnionAndDistinct()
{
Tuple<IEnumerable<RegisteredDriver>, IEnumerable<Car>> tuple =
GetDriversAndCars();
IEnumerable<RegisteredDriver> drivers = tuple.Item1;
IEnumerable<Car> cars = tuple.Item2;
List<RegisteredDriver> list = drivers.Join(
cars,
d => d.LicenseNumber,
c => c.OwnersLicenseNumber,
(d,c) => new RegisteredDriver()
{
LicenseNumber = d.LicenseNumber,
LicenseExpiration = d.LicenseExpiration,
IsRequiredToWearGlasses = d.IsRequiredToWearGlasses,
Cars = cars.Where(x => x.OwnersLicenseNumber ==
d.LicenseNumber).ToList()
}
).Distinct().Union(drivers).ToList();
Assert.AreEqual(list.Count, 5);
Assert.AreEqual(list[0].LicenseNumber, 33489432);
Assert.AreEqual(list[0].Cars.Count(), 1);
Assert.AreEqual(list[0].Cars.ElementAt(0).LicensePlate, "DIE HOT");
Assert.AreEqual(list[1].LicenseNumber, 90343534);
Assert.AreEqual(list[1].Cars.Count(), 2);
Assert.AreEqual(list[1].Cars.ElementAt(0).LicensePlate, "888 BAR");
Assert.AreEqual(list[1].Cars.ElementAt(1).LicensePlate, "FOO 42X");
Assert.AreEqual(list[2].LicenseNumber, 54234989);
Assert.AreEqual(list[2].Cars.Count(), 1);
Assert.AreEqual(list[2].Cars.ElementAt(0).LicensePlate, "ABC 123");
Assert.AreEqual(list[3].LicenseNumber, 83242343);
Assert.AreEqual(list[3].Cars.Count(), 0);
Assert.AreEqual(list[4].LicenseNumber, 29423477);
Assert.AreEqual(list[4].Cars.Count(), 0);
}
[TestMethod]
public void JoinTestWithDefaultIfEmptyAndDistinct()
{
Tuple<IEnumerable<RegisteredDriver>, IEnumerable<Car>> tuple =
GetDriversAndCars();
IEnumerable<RegisteredDriver> drivers = tuple.Item1;
IEnumerable<Car> cars = tuple.Item2;
List<RegisteredDriver> list = (from d in drivers
join c in cars on d.LicenseNumber equals c.OwnersLicenseNumber into gunk
from g in gunk.DefaultIfEmpty()
select new RegisteredDriver
{
LicenseNumber = d.LicenseNumber,
LicenseExpiration = d.LicenseExpiration,
IsRequiredToWearGlasses = d.IsRequiredToWearGlasses,
Cars = cars.Where(x => x.OwnersLicenseNumber ==
d.LicenseNumber).ToList()
}).Distinct().ToList();
Assert.AreEqual(list.Count, 5);
Assert.AreEqual(list[0].LicenseNumber, 33489432);
Assert.AreEqual(list[0].Cars.Count(), 1);
Assert.AreEqual(list[0].Cars.ElementAt(0).LicensePlate, "DIE HOT");
Assert.AreEqual(list[1].LicenseNumber, 90343534);
Assert.AreEqual(list[1].Cars.Count(), 2);
Assert.AreEqual(list[1].Cars.ElementAt(0).LicensePlate, "888 BAR");
Assert.AreEqual(list[1].Cars.ElementAt(1).LicensePlate, "FOO 42X");
Assert.AreEqual(list[2].LicenseNumber, 83242343);
Assert.AreEqual(list[2].Cars.Count(), 0);
Assert.AreEqual(list[3].LicenseNumber, 29423477);
Assert.AreEqual(list[3].Cars.Count(), 0);
Assert.AreEqual(list[4].LicenseNumber, 54234989);
Assert.AreEqual(list[4].Cars.Count(), 1);
Assert.AreEqual(list[4].Cars.ElementAt(0).LicensePlate, "ABC 123");
}
public Tuple<IEnumerable<RegisteredDriver>, IEnumerable<Car>>
GetDriversAndCars()
{
IEnumerable<RegisteredDriver> drivers = new List<RegisteredDriver>()
{
new RegisteredDriver()
{
LicenseNumber = 33489432,
LicenseExpiration = new DateTime(2013, 6, 15),
IsRequiredToWearGlasses = true,
Cars = new List<Car>()
},
new RegisteredDriver()
{
LicenseNumber = 90343534,
LicenseExpiration = new DateTime(2012, 12, 7),
IsRequiredToWearGlasses = false,
Cars = new List<Car>()
},
new RegisteredDriver()
{
LicenseNumber = 83242343,
LicenseExpiration = new DateTime(2012, 7, 31),
IsRequiredToWearGlasses = false,
Cars = new List<Car>()
},
new RegisteredDriver()
{
LicenseNumber = 29423477,
LicenseExpiration = new DateTime(2012, 8, 29),
IsRequiredToWearGlasses = false,
Cars = new List<Car>()
},
new RegisteredDriver()
{
LicenseNumber = 54234989,
LicenseExpiration = new DateTime(2013, 1, 14),
IsRequiredToWearGlasses = false,
Cars = new List<Car>()
}
};
IEnumerable<Car> cars = new List<Car>()
{
new Car()
{
YearMakeModel = "1994 Daihatsu Charade",
LicensePlate = "DIE HOT",
RegistrationExpiration = new DateTime(2012, 8, 16),
IsInsured = true,
OwnersLicenseNumber = 33489432
},
new Car()
{
YearMakeModel = "2011 Ford Taurus",
LicensePlate = "888 BAR",
RegistrationExpiration = new DateTime(2012, 12, 30),
IsInsured = true,
OwnersLicenseNumber = 90343534
},
new Car()
{
YearMakeModel = "2005 Honda Accord",
LicensePlate = "FOO 42X",
RegistrationExpiration = new DateTime(2010, 11, 11),
IsInsured = false,
OwnersLicenseNumber = 90343534
},
new Car()
{
YearMakeModel = "2003 Nissan Centra",
LicensePlate = "ABC 123",
RegistrationExpiration = new DateTime(2012, 9, 3),
IsInsured = true,
OwnersLicenseNumber = 54234989
}
};
return new Tuple<IEnumerable<RegisteredDriver>,
IEnumerable<Car>>(drivers,cars);
}
}
}
Let's talk through the four tests:
- JoinTest gives us only RegisteredDrivers who have Cars and the RegisteredDrivers get duplicate entries for every Car that they own. In order for matching to work Car carries on it the driver's license number of its owner.
using System;
namespace LinqStuff.Core
{
public class Car
{
public string YearMakeModel { get; set; }
public string LicensePlate { get; set; }
public DateTime RegistrationExpiration { get; set; }
public bool IsInsured { get; set; }
public int OwnersLicenseNumber { get; set; }
}
}
This is a basic Join with poorly shaped end data. We can do better! Shall we say that our end goal should be to get all of the RegisteredDrivers without duplicates and without dropping RegisteredDrivers which do not have Cars? I tinkered with how to accomplish this and ultimately concluded I would need to look beyond the Join itself.
- JoinTestWithDistinct uses .Distinct() to get rid of duplicate RegisteredDrivers, which beneath the hood does comparison operations against RegisteredDrivers and drops perceived duplicates. In order to get this to work, I had to change RegisteredDriver from a class to a struct as a class is a reference object and two different references to a RegisteredDriver holding identical getsetter settings which nonetheless occupy different places on the heap will not be seen as equal. The switch to struct alone was not enough. I am not sure why. I suspect it is because one of the getsetters holds a collection of classes (Car) and thus will have the same matching problem as RegisteredDriver would in class form. In the end, I overrode the Equals method on RegisteredDriver to ensure that equality comparisons compared just one of the getsetters in the struct instead of all of them.
using System;
using System.Collections.Generic;
namespace LinqStuff.Core
{
public struct RegisteredDriver
{
public int LicenseNumber { get; set; }
public DateTime LicenseExpiration { get; set; }
public bool IsRequiredToWearGlasses { get; set; }
public IEnumerable<Car> Cars { get; set; }
public override bool Equals(object other)
{
return LicenseNumber == ((RegisteredDriver)other).LicenseNumber;
}
}
}
This test also implements a Select when assigning Cars to a particular RegisteredDriver so that a RegisteredDriver gets every car it owns. In the previous test we basically had flat data, but now we have hierarchal data. The problem we have not beat however is one of how to include RegisteredDrivers that have no Cars.
- JoinTestWithUnionAndDistinct adds the RegisteredDrivers without Cars to our collection by way of a Union. A Union between two collections will produce no duplicates even if duplicates exist (not so with Concat) and thus if one does a Union of a set of RegisteredDrivers without Cars and a subset of that set in which the RegisteredDrivers are assigned Cars the end result will more or less mirror the first set save that some of the RegisteredDrivers will have Cars. One change up may be the ordering. Also, I approached the Union like so:
subset.Union(fullset)
Doing the Union the other way around would have cost us the Car data.
- JoinTestWithDefaultIfEmptyAndDistinct uses .DefaultIfEmpty() in fluent syntax to do what Union did in the prior test (with ultimately different ordering). I do not know how to emulate .DefaultIfEmpty() in a Lambda as of yet. There may not be a way.