# Sunday, February 05, 2012

I wanted to use a many-to-many relationship using Entity Framework Code First (v4.1/4.2).

Using pure code first such as this:

using (var context = new MyContext())
{
    var employee = new Employee { FirstName = "Homer", LastName = "Simpson" };
    var territory = new Territory { TerritoryDescription = "Springfield" };
    employee.Territories.Add(territory);
    context.Employees.Add(employee);

    context.SaveChanges();
}

results in a nice association table

image

How do you map existing database tables? Like Northwind's customer to customer demographic table relationship:

image

This is the code I want to write:

using (var context = new MyContext("name=Northwind"))
{

    var demo = new CustomerDemographic();
    demo.CustomerTypeID = "BERLIN";
    demo.CustomerDesc = "Berliner";
    context.CustomerDemographics.Add(demo);
    //link it to a customer by either end
    var alfki = context.Customers.Find("ALFKI");
    alfki.CustomerDemographics.Add(demo);

    context.SaveChanges();
}

We have to override DbContext's OnModelCreating and add some mapping. For CodeFirst, you map both sides of the relationship, so you can either put the mapping on Customer or CustomerDemographic - or even both if the mappings agree. A normal foreign key relationship is mapped with ".HasMany|HasOptional|HasRequired" followed by a ".WithMany|WithOptional|WithRequired".

So, from the CustomerDemographic entity, a many to many is just .HasMany(x=>x.Customers).WithMany(z=>z.CustomerDemographics).

In addition, we don't have standard names for our association table so we add a .Map element to specify the table and the left and right key columns.

Note the primary key of CustomerDemographics isn't the 'tableName'+"Id" convention that Code First will expect. So I have to define the key for that. As we have that end of the configuration, we'll define the mapping there.

Here's the code.

modelBuilder.Entity<CustomerDemographic>()
    //the key isn't standard so specify it
    .HasKey(x => x.CustomerTypeID)
    //define both sides of the relationship - HasMany.WithMany
    .HasMany(x => x.Customers)
    .WithMany(z => z.CustomerDemographics)
    //specify mapping information
    .Map(map =>
    {
        //the association table name
        map.ToTable("CustomerCustomerDemo");
        //the left side (fk to CustomerDemographic, the entity we're defining)
        map.MapLeftKey("CustomerTypeID");
        //the right side (fk to Customers, the other side)
        map.MapRightKey("CustomerID");
    }
);

If we mapped from the Customer entity, the HasMany and WithMany properties are different, and the mapped left and right keys swap round.

Here's the full DbContext for my mini-Northwind mapping:

class MyContext : DbContext
{
    public MyContext(string connectionName)
        : base(connectionName)
    {
    }

    public DbSet<Employee> Employees { get; set; }
    public DbSet<Territory> Territories { get; set; }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<CustomerDemographic> CustomerDemographics { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
        Database.SetInitializer<MyContext>(null);
        modelBuilder.Conventions.Remove<System.Data.Entity.Infrastructure.IncludeMetadataConvention>();

        modelBuilder.Entity<CustomerDemographic>()
            //the key isn't standard so specify it
            .HasKey(x => x.CustomerTypeID)
            //define both sides of the relationship - HasMany.WithMany
            .HasMany(x => x.Customers)
            .WithMany(z => z.CustomerDemographics)
            //specify mapping information
            .Map(map =>
            {
                //the association table name
                map.ToTable("CustomerCustomerDemo");
                //the left side (fk to CustomerDemographic, the entity we're defining)
                map.MapLeftKey("CustomerTypeID");
                //the right side (fk to Customers, the other side)
                map.MapRightKey("CustomerID");
            }
        );
    }
}
posted on Sunday, February 05, 2012 9:42:13 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Thursday, January 05, 2012
 
//find the working folder for each TFS
var projectCollections = RegisteredTfsConnections.GetProjectCollections();
 
foreach (var registeredProjectCollection in projectCollections)
{
    Console.WriteLine("Project collection: {0} {1}", 
registeredProjectCollection.Name,
registeredProjectCollection.Uri.AbsoluteUri);     var projectCollection =         TfsTeamProjectCollectionFactory.GetTeamProjectCollection(registeredProjectCollection);     var versionControl = projectCollection.GetService<VersionControlServer>();     // get workspace     var workspace =         versionControl.QueryWorkspaces(null,                 System.Threading.Thread.CurrentPrincipal.Identity.Name,                 Environment.MachineName)                 .FirstOrDefault(x =>                      x.Folders.Length > 0 &&                     //if there is a Work Item Manager, we don't care                     x.Name != "WIM (" + Environment.MachineName + ")");     if(workspace == nullcontinue//no workspace for this server     //there's normally only one     WorkingFolder folder = workspace.Folders.First();     Console.WriteLine("Working folder {0}", folder.LocalItem); }

//old VS 2008 way //using (TeamFoundationServer tfsServer = new TeamFoundationServer(tfsServerName)) //{ //    // Get a reference to version control //    VersionControlServer versionControl = //        (VersionControlServer) tfsServer.GetService(typeof (VersionControlServer)); //    //.... //}
posted on Thursday, January 05, 2012 9:51:17 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Saturday, December 10, 2011

I've just done all 4 of the exams for .Net 4.0 MCPD Web Developer. Overall, I quite enjoyed it. I even learned a bit.

Programmer certifications, and particularly the Microsoft ones, get a lot of criticism. In theory they prove someone is proficient in the technology. In practice, by itself a certificate is unreliable.

Microsoft's Partner program insists that companies have staff with certifications. The body shops put their new recruits through a brief course so they can pass and then be sold on. Often, they memorised the questions and answers from internet dumps. It's disappointing to google the exam codes: apart from the Microsoft syllabus, every link is a site selling the answers.

If you're a consultant, or you're going to change jobs, experienced programmers probably have to do the exams too. If you work for a Microsoft Partner you have to do it, and HR departments filter their CVs using it. Really, the best measure of proficiency is years of experience and types of projects. That's real-world practical knowledge. Okay, I would say that, having been doing this for 25 years. But, as the company I work for needs the certificate points, and they're paying, I'm happy to do the exams.

For .net 1 we had to do exams for ASP, windows forms and web services, but most .net programmers then only worked in one or two of those- certainly never windows forms and ASP. I was actually quite impressed by the .Net 2 foundation exam, which covered a lot of basics: collections, threads, tracing, serialization, globalization, encryption. Specific projects may not involve some of it, but a good .net programmer should know almost all of it (even if you have to google the details). Unfortunately they dropped it for .Net 4, going back to technology areas (asp, web services) just like the original .net 1 exams.  Now for .net 4 "web" we have asp, wcf and data access (ado/entity framework). Broadly I agree you'd expect all web developers to know those topics, so that's not too bad.

One big problem with certification questions is that they pick on obscure APIs and ASP controls. Now, questions on the validation controls is probably a good basic requirement, but frankly the gridview events are a nightmare anyway, so memorizing them for an exam is painful. The .net 4 asp exam wasn't too bad although there was some API questions that in real life you'd just google. One of the jQuery questions annoyed me too, using an unusual API detail (I think an older syntax) when they could have used a better, clearer replacement.

The older exams had sections devoted to things nobody ever uses. On the asp .net 2 exam it was mobile controls, and web parts (cloned from Sharepoint, and never used outside Sharepoint). Fortunately there seems less of that in the .net 4 exams, although there's still some asp ajax library stuff in there, plus some dynamic data. The big problem was that jQuery and MVC have moved on since the exam was written. All the questions were about MVC 2, not 3. It's not that the questions were obsolete (there was nothing much about webform views to confuse those who use Razor), it's just that the pace of change is making exams less relevant.

The other exams had little used topics too. The WCF exam covered MSMQ, which I've never seen used in real life. The data access exam was more problematic. Basic ADO is useful, but there was still material about little used dataset features like constraints and dataRelations. There was not much on Linq2Sql, perhaps just as well, but most of the exam was Entity Framework. I know EF is used out there, but I've yet to encounter EF and it seems to be in second place to NHibernate at least for ORM data access. So I did learn something for this exam, and yes, I passed the exam as a novice with no real world experience of Entity Framework. On the other hand, as an experienced programmer I'd feel comfortable doing EF (even if I'd rather be doing NHibernate.)

Two of the exams - the WCF and the ASP Pro exam - had "testlets" with a case study and a small number of questions about the scenario. The WCF was over-elaborate, with source listings and xml, but for all that I thought it was a little more realistic and actually quite enjoyable.

To study, I used the Microsoft Training Kit books, MSDN and for the areas I was less familiar with - mostly EF and some aspects of WCF - a little practice. The WCF exam didn't have a book, so I had to use the .net 3.5 one and look up a couple of subjects (routing and discovery). I skimmed the bits I knew well (the asp/ web side generally). Generally I found you needed a bit more knowledge than was in the books, but only a few things were really obscure. Overall, studying for the exam was interesting and useful. Learning about WCF routing and discovery and EF, things I haven't actually used, did bring me more up-to-date with the .net 4 stack.

A pity Microsoft's "congratulations" email contains a link to a blog that closed 2 years ago.

posted on Saturday, December 10, 2011 10:15:55 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Friday, July 08, 2011
A brief recap of ASP.Net cache:

//in MVC use HttpRuntime.Cache or HttpContext.Cache
//in webforms Application is the original cache without expiration rules
var category = HttpRuntime.Cache["Category"as CategoryModel;
if (category == null)
{
     category = _dataAccess.Find(1);
     HttpRuntime.Cache["Category"] = category;
     // ...or...
     //monitor some files and/or other cache items
     var cd = new CacheDependency(new[] { @"C:\triggerFolder\" }, new[] { "OtherCacheItem" });
     HttpRuntime.Cache.Insert("Category1", category, 
               cd, //dependencies or null
               DateTime.Now.AddMinutes(5), //absolute expiration (or Cache.NoAbsoluteExporation)
             Cache.NoSlidingExpiration //sliding expiration (timespan)
     );
}
Mocking this in tests (especially for MVC) is a bit ugly (.Net 3.5sp1 has System.Web.Abstractions including HttpContextBase, but caching isn't included)

Now in .Net 4 we can reference System.Runtime.Caching.dll. And the really nice thing is this will run outside Asp.Net.
//get the static "default" cache. You can have multiple named caches.
ObjectCache cache = MemoryCache.Default;
//you can't store null in the cache
var category = cache["Category"as CategoryModel;
if (category == null)
{
    category = _dataAccess.Find(1);
    cache["Category"] = category;
    // ...or...
    var policy = new CacheItemPolicy();
    policy.AbsoluteExpiration = DateTime.Now.AddMinutes(5);
    //monitor some files and/or other cache items
    policy.ChangeMonitors.Add(
        new HostFileChangeMonitor(new List<string> { @"C:\triggerFolder\"})
        );
    //synchronize with another cache item
    policy.ChangeMonitors.Add(
        cache.CreateCacheEntryChangeMonitor(new [] { "OtherCacheItem"})
        );
    cache.Add("Category1", category, policy);
}
For simple caching (no change monitors) you don't even need mocking in your tests - in fact, you can test your caching with a real cache. You can move caching down into your library classes that may be called from web pages, tests, WPF apps and consoles.



posted on Friday, July 08, 2011 10:57:57 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Friday, June 10, 2011
T4 preprocessed templates are a neat way of generating text at run time, which can be deployed to machines without Visual Studio. Here's MSDN

Here's a simple template, ClassWriter.tt, which must be marked CustomTool = "TextTemplatingFilePreprocessor" in properties (not "TextTemplatingFileGenerator" which is a normal T4).
<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
<#@ parameter type="Generator.Model.Table" name="table" #>
using System;
 
namespace <#= table.Namespace #>
{
    [Serializable]
    public class <#= table.Name #>
    {
<#  foreach(var column in table.Columns.Where(c=> !c.Hidden)) { #>
        public virtual <#= column.Type #> <#= column.Name #> { get; set; }
<#    } #>
    }
}

At development time, Visual Studio generates the class in the corresponding namespace which you can then call (yeah, it generates code for generating code...).

Notice we're passing a parameter, which has to have the full namespaced name (even "string" has to be "System.String").

The generated class is partial and the parameters are property getters with a backing field. MSDN suggests that to pass in your parameters you should manually code a partial class with conventional properties or a constructor. Actually, there's an easier no-code way. Use the Session property (which is just a Dictionary<string, object>) which you can use with an Initialize() method. Like this:

//create the class generated by TextTemplatingFilePreprocessor 
var generator = new ClassWriter();
//create a session dictionary, fill it and initialize
generator.Session = new Dictionary<stringobject>();
generator.Session.Add("table", table);
generator.Initialize();
//transform!
var text = generator.TransformText();
The key things to watch out for:
  • you must create the Session dictionary (it's not initialized internally)
  • you must call Initialize() after it's populated.

You can also use System.Runtime.Remoting.Messaging.CallContext but weirdly you must still initialize the Session dictionary (Initialize checks Session first, then CallContext).
 
You may be tempted to reuse the T4 template class like this.
//don't do this
var generator = new ClassWriter();
foreach (var item in list)
{
    generator.Session = new Dictionary<stringobject>();
    generator.Session.Add("table", item);
    generator.Initialize();
    var txt = generator.TransformText();
    WriteText(item, txt);
}
The template class actually uses an internal StringBuilder called GenerationEnvironment. So each call returns everything you wrote before. You can't actually reset the StringBuilder (although you can append to it with Write overloads). So, always create the template class within the loop.



posted on Friday, June 10, 2011 1:50:55 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, May 02, 2011
Strong named assemblies cannot reference assemblies which aren't strong named.

Decompile with ildasm and recompile with ilasm using your key.

Default ILDASM and ILASM locations as of .Net 4.0

"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe" ClassLibrary.dll /out:ClassLibrary.il
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" ClassLibrary.il /res:ClassLibrary.res /dll /key:myKey.snk /out:ClassLibrary.dll

posted on Monday, May 02, 2011 2:50:05 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, April 25, 2011

Shortly after the last release of Database Schema Reader, here's another release.

Last time I added a CopyToSQLite tool, which reads (almost) any database and creates a SQLite clone. I also added experimental support for SQLServer CE 4.0, but it had some big limitations.

So the obvious next step was to fix some of those limitations, and that's what this release focuses on. Unfortunately CopyToSQLite.exe is now a little misnamed. Winking smile

I added a little bit of conversion code so VARCHAR(MAX) found in the origin database gets converted to NTEXT (and VARBINARY to IMAGE). The reading of SQLServer CE 4 databases got improved too (capturing the  foreign and unique keys and identity columns).

SQLServer allows an integer column to be marked as "Identity" so it will be auto-generated (the equivalent in Oracle is to create a sequence and an insert trigger). But you can't include the identity column when you insert a row. So, when cloning data, SQLServer also allows identity-inserts with SET IDENTITY_INSERT [MyTable] ON/OFF. When you've done that, you should reset the identity seed with DBCC CHECKIDENT.

But in SQLServer CE 4, there is no DBCC CHECKIDENT. You have to do an ALTER TABLE [MyTable] ALTER COLUMN [IdentityColumn] IDENTITY (999,1) (where 999 is the new max(IdentityColumn)). It's another of those little gotchas between SQLServer and SQLServer CE.

SQLServer CE 4 has some great new paging syntax:

SELECT Id, Name FROM MyTable
ORDER BY Name
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY

It's similar to the LIMIT/OFFSET syntax in MySQL and SQLite, but (reasonably enough) must follow an ORDER BY. Rather than offset/skipping a number of rows, I'd like to specify just page number and page size - or another formula. The SQLServer 2011/Denali documentation shows the offset/fetch syntax for a start row/end row expression:

SELECT DepartmentID, Name, GroupName
FROM HumanResources.Department
ORDER BY DepartmentID ASC
OFFSET @StartingRowNumber - 1 ROWS
FETCH NEXT @EndingRowNumber - @StartingRowNumber + 1 ROWS ONLY

But that doesn't work in SQLServer CE 4. You can use parameters, or constants, or expressions with constants, but apparently not expressions with constants and parameters. Oh well, it's still much better than horrible OVER subqueries.

Overall, the "CopyToSQLite" to SQLServer CE 4 seems to work well. Using easily deployable file databases like SQLServer CE and SQLite is simple and powerful.

posted on Monday, April 25, 2011 12:13:17 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Saturday, April 09, 2011

My Codeplex project, Database Schema Reader, has a new version.

I needed to create a SQLite database was a replica of a parent SQL Server database. The existing project could easily read the schema, and the code generation tools could give me the table DDL and the insert SQL. It was just a matter of executing the SQL and creating the database file.

So I created another simple Windows Forms UI. The SqlWriter class needed some tweaks for SQLite support (and I added integration tests).

For fun, I decided to see if it could also support Microsoft's latest version of SQL Server CE 4.0.

SQL Server CE 4.0 is, like SQLite, an in-process database which can be XCOPY deployed. And unlike previous versions, it is easily able to run ASP.Net websites (it's the default database behind WebMatrix). And 4.0 provides the standard ADO GetSchema which my Database Schema Reader uses (3.5 throws a Not Implemented exception).

It has some of the same limitations as SQLite, such as no stored procedures (good riddance), and no output parameters. But it also has additional limitations. In SQLite you can batch together multiple SQL statements in a single line and execute them in one command. You can't in SQL Server CE.

Unlike SQLite's weakly typed and very simple data types, SQL Server CE 4.0 has most of the standard data types as SQL Server Express and full versions. Unfortunately, this was the big problem. If your table is defined with an IDENTITY primary key, you can't insert the data row with the same primary key, and subsequent foreign key relationships are broken. It's a pretty critical limitation for my scenario.

SQL Server CE 4 only supports a subset of SQL Server data types. The one data type it didn't support, which my database (and later versions of Northwind) use, is NVARCHAR(MAX). You have to use the horrible old NTEXT data type. I could have written a DDL provider that translates varchar max to ntext, but I don't think it's worth the effort.

So my SQL Server CE creation program has major limitations, compared to the SQLite version. It's built into CopyToSQLite.exe, and auto-detects if you've installed SQL Server CE, but you have to have a very simple database for it to work.

SQL Server CE 4 also doesn't support the ADO Sync framework which allows you to synchronize a disconnected database to a parent SQL Server database. Even SQL Server 2008 R2 Management Studio doesn't support it (you have to use Visual Studio 2010 with SP1).

The new FETCH /OFFSET syntax for paging, that will be SQL Server 11, is very nice though. I'm looking forward to that.

posted on Saturday, April 09, 2011 8:05:41 PM (Romance Daylight Time, UTC+02:00)  #    Comments [1]
# Thursday, April 07, 2011
VS 2010 web.config transformations are great.
Change your development web.config which looks like this:

<?xml version="1.0"?>
 
<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
 
  <system.web>
    <compilation debug="true" targetFramework="4.0">

by adding a Web.DeployTest.config file which looks like this:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
 
  <connectionStrings>
    <add name="ApplicationServices" connectionString="Data Source=StaticVoidSqlServer;Initial Catalog=Northwind;Integrated Security=SSPI;" providerName="System.Data.SqlClient"
         xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>
 
</configuration>

Only by default it's tied up with the build configurations (Debug, Release etc) and the Web deploy story.
But I don't want a web deployment package. I want:
  • a normal "Release" configuration build
  • a directory containing all the website files that I can XCopy deploy (like old school Visual Studio 2008 publish)
  • the web.config transformed with my custom "DeployTest" name.
Doing it this way means the web.DeployTest.config is not automatically nested under the web.config (and I should mark it as BuildAction=None).
But that's my requirements. So I wrote an MSBuild file.

Batch file (build.bat)

First here's a standard batch file, "build.bat", to launch it (I want MSBuild 4.0):
%systemroot%\Microsoft.Net\Framework\v4.0.30319\MSBuild.exe build.proj  /t:Release & pause

MSBuild script (build.proj)


<?xml version="1.0" encoding="utf-8" ?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Release">   <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />   <PropertyGroup>     <!-- properties that are used in this build file - referenced as $(PropertyName) -->     <ProjectName>StaticVoid</ProjectName>     <DeployConfiguration>DeployTest</DeployConfiguration>     <PublishPath>$(MSBuildProjectDirectory)\..\Publish\</PublishPath>     <OutputPath>$(PublishPath)\StaticVoid\</OutputPath>     <PackagePath>$(PublishPath)\StaticVoidPackage\</PackagePath>     <TransformInputFile>..\StaticVoid\Web.config</TransformInputFile>     <TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>     <TransformOutputFile>$(OutputPath)\Web.config</TransformOutputFile>     <ImageResourcesPath>..\ImageResources</ImageResourcesPath>   </PropertyGroup>   <ItemGroup>     <ImageResources Include="$(ImageResourcesPath)\*.jpg" />   </ItemGroup>   <!-- targets -->   <Target Name="PublishWebsite">     <Message Text="Publishing Website" />     <RemoveDir Directories="$(PublishPath)"/>     <!-- do a deploy -->     <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;OutputPath=$(PackagePath);DeployOnBuild=true;DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;AutoParameterizationWebConfigConnectionStrings=false;_PackageTempDir=$(OutputPath)"/>   </Target>   <Target Name="Transform">     <!-- transform the web.config -->     <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />   </Target>   <Target Name="BuildWebsite" DependsOnTargets="PublishWebsite">     <!-- we don't need the deployment package, we wanted the published files to copy manually -->     <RemoveDir Directories="$(PackagePath)"/>     <!-- copy the unmanaged resources -->     <Copy SourceFiles="@(ImageResources)" DestinationFolder="$(PublishPath)images" SkipUnchangedFiles="true" />   </Target>      <Target Name="Release" DependsOnTargets="BuildWebsite; Transform;">   </Target> </Project>
 

A little explanation

The MSBuild task for the website project (StaticVoid.csproj) has a whole set of extra properties set which make it do a deploy.
(Broken here to be easier to read:)
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj" Properties="Configuration=Release;
OutputPath=$(PackagePath);
DeployOnBuild=true;
DeployTarget=PipelinePreDeployCopyAllFilesToOneFolder;
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=$(OutputPath)
"/>

The regular deployment package is written to $(PackagePath). I don't care about that, so I delete it.
The actual directory and files that I wanted are written to $(OutputPath) using the _PackageTempDir property.

For the transform, note the msbuild xml must have Project ToolsVersion="4.0" and
<UsingTask TaskName="TransformXml" 
AssemblyFile
="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
And then the transformation:
    <TransformXml Source="$(TransformInputFile)"                   Transform="$(TransformFile)"                   Destination="$(TransformOutputFile)" />
with $(TransformFile) defined as
<TransformFile>..\StaticVoid\Web.$(DeployConfiguration).config</TransformFile>
Simply changing the DeployConfiguration property lets me have test and production builds which transform things nicely.

Update: the old _CopyWebApplication still works too

In Visual Studio 2008 you could use this task:
    <MSBuild Projects="..\StaticVoid\StaticVoid.csproj"
             Targets="ResolveReferences;_CopyWebApplication"
             Properties="Configuration=Release;
                         WebProjectOutputDir=$(OutputPath);
                         OutDir=$(OutputPath)\bin\" />
This still works in Visual Studio 2010's MSBuild (and you don't have a package directory to delete).
The transforms can still be done manually.


posted on Thursday, April 07, 2011 12:34:35 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Monday, February 14, 2011

My little Codeplex project has had a few updates lately, with a new release tonight. This weekend I was transferring a SqlServer database onto MySQL, so I fixed up the SQL generation capabilities and added it to the UI.

Simply, it now can read the SqlServer schema (or almost any other ADO database), and write out the DDL for a MySQL database. Or an Oracle one. It should also work from Oracle to SqlServer etc.

Of course this only works for simple databases. Mine had a couple of uniqueidentifer columns (GUIDs), which don't translate to anything other than in SqlServer, so that was fixed up manually. Check constraints can be problematic, and it doesn't touch views or, God forbid, triggers and stored procedures. It won't roundtrip accurately (say SqlServer to MySql to SqlServer) because we are generalizing datatypes each time.

It works on my database. Winking smile

posted on Monday, February 14, 2011 10:02:01 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]