1 using System;
   2 using System.Collections.Generic;
   3 using System.Data;
   4 using DomainObjects.DbAccess.Command.SqlStatement.ObjectModel.From;
   5 using DomainObjects.Facade.Command;
   6 using DomainObjects.Facade.Command.Expression;
   7 using DomainObjects.Facade.Test;
   8 using DomainObjects.Test.Domain;
   9 using NUnit.Framework;
  10 
  11 namespace DomainObjects.Test.TestFixture
  12 {
  13   /// <summary>
  14   /// Tests the generation and behavior of the field reference paths
  15   /// generated by the DomainObjectGen.exe tool.
  16   /// </summary>
  17   [TestFixture]
  18   public class GeneratedReferencePathTests : DomainObjectsTestFixture
  19   {
  20     /// <summary>
  21     /// Validates that a <see cref="Field"/> can be stored and retrieved by key.
  22     /// </summary>
  23     [Test]
  24     public void FieldCaching()
  25     {
  26       // Create a field that will be cached
  27       Field fieldToCache = Types.Product._productCategory.CrossJoin.BaseType.Field._primaryKey;
  28 
  29       // After the call to GetKey(), the field
  30       // should be cached.
  31       string fieldKey = fieldToCache.GetKey();
  32 
  33       // Retrieve the field from the cache
  34       Field retrievedField = Field.GetField(fieldKey);
  35 
  36       // Validate that the original field and the
  37       // retrieved field are the same field
  38       Assert.AreEqual(fieldToCache, retrievedField, "The field that was cached is the same field that was retrieved.");
  39     }
  40 
  41     /// <summary>
  42     /// Validates that a subclass can be referenced from the root base class
  43     /// in a reference path. Note that this will generate a cross join between
  44     /// the base class and the sub class and each type will be represented by
  45     /// a different table alias.
  46     /// </summary>
  47     [Test]
  48     public void RootOfPathAllowsSubClasses()
  49     {
  50       Query<Guitar> guitarQuery = new Query<Guitar>();
  51       guitarQuery.Where.AddNotNull(Types.Guitar.Field._primaryKey);
  52       // This reference path ends up replacing the Guitar class with the AcousticGuitar sub class
  53       guitarQuery.Where.AddGreaterThan(Types.Guitar.SubClass.AcousticGuitar.Field._numberOfStrings, 0);
  54       List<Guitar> guitars = guitarQuery.FindObjectSet();
  55       Assert.Greater(guitars.Count, 0, "Found some guitars.");
  56     }
  57 
  58     [Test]
  59     public void SingleSubClassAtRootOfPathCorrectlyConstrained()
  60     {
  61       // Create a set of classes where the base-type matches
  62       // a constraint but the sub-type is more constrained
  63       ProductCategory guitarCategory = new ProductCategory(GenerateUniqueString(Types.ProductCategory.Field._name), GenerateUniqueString(Types.ProductCategory.Field._description));
  64       string manufacturerOne = GenerateUniqueString(Types.Guitar.Field._manufacturer);
  65       Guitar guitar = new Guitar(guitarCategory, manufacturerOne, 3);
  66 
  67       string bodyDescription = GenerateUniqueString(Types.AcousticGuitar.Field._bodyDescription);
  68       AcousticGuitar acousticGuitar = new AcousticGuitar(guitarCategory, manufacturerOne, 3, bodyDescription);
  69 
  70       PersistenceFacade.Persist(guitarCategory, guitar, acousticGuitar);
  71 
  72       Query<Guitar> guitarQuery = new Query<Guitar>();
  73       // Matches two rows in the guitar table
  74       guitarQuery.Where.AddEqualTo(Types.Guitar.Field._manufacturer, manufacturerOne);
  75       // Only matches one row, therefore only one row should be returned
  76       guitarQuery.Where.AddEqualTo(Types.Guitar.SubClass.AcousticGuitar.Field._bodyDescription, bodyDescription);
  77 
  78       Assert.AreEqual(1, guitarQuery.GetDataSetCount(), "Found exactly one guitar that has the constrained body description.");
  79     }
  80 
  81     [Test]
  82     public void MultipleSubClassesAtRootOfPathCorrectlyConstrained()
  83     {
  84       // Create a set of classes where the base-type matches
  85       // a constraint but the sub-type is more constrained
  86       SpanishProductCategory guitarCategory = new SpanishProductCategory(GenerateUniqueString(Types.ProductCategory.Field._name), GenerateUniqueString(Types.ProductCategory.Field._description), GenerateUniqueString(Types.SpanishProductCategory.Field._spanishDescription));
  87       string manufacturerOne = GenerateUniqueString(Types.Guitar.Field._manufacturer);
  88       Guitar guitar = new Guitar(guitarCategory, manufacturerOne, 3);
  89 
  90       string bodyDescription = GenerateUniqueString(Types.SpanishAcousticGuitar.Field._bodyDescription);
  91       SpanishAcousticGuitar acousticGuitar = new SpanishAcousticGuitar(guitarCategory, manufacturerOne, 3, bodyDescription);
  92 
  93       PersistenceFacade.Persist(guitarCategory, guitar, acousticGuitar);
  94 
  95       Query<Guitar> guitarQuery = new Query<Guitar>();
  96       // Matches two rows in the guitar table
  97       guitarQuery.Where.AddEqualTo(Types.Guitar.Field._manufacturer, manufacturerOne);
  98 
  99       // Only matches one row, therefore only one row should be returned
 100       guitarQuery.Where.AddEqualTo(Types.Guitar.SubClass.AcousticGuitar.Field._bodyDescription, bodyDescription);
 101 
 102       // Constrain the same field on the sub-type. We still should be referring to the same row
 103       guitarQuery.Where.AddEqualTo(Types.Guitar.SubClass.AcousticGuitar.SubClass.SpanishAcousticGuitar.Field._bodyDescription, bodyDescription);
 104 
 105       Assert.AreEqual(1, guitarQuery.GetDataSetCount(), "Found exactly one guitar that has the constrained body description.");
 106     }
 107 
 108     /// <summary>
 109     /// Validates that when a base class left outer joins
 110     /// its sub class, that a subclass type constraint is not applied
 111     /// to the query.
 112     /// </summary>
 113     [Test]
 114     public void LeftOuterJoinSubClass()
 115     {
 116       // Create an instance of a 'ProductCategory' that
 117       // is related to an instance of 'Guitar'
 118       ProductCategory productCategory = new ProductCategory("Nylon String Guitars", "Acoustic Guitars that have nylon strings");
 119       Guitar guitar = new Guitar(productCategory, "McPherson", 6);
 120 
 121       // Persist the new category and guitar
 122       PersistenceFacade.Persist(productCategory, guitar);
 123 
 124       // Now find the 6 string guitars associated with the new
 125       // product category we created above. (There should be only one)
 126       Query<Guitar> query = new Query<Guitar>();
 127       query.Where.AddEqualTo(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.Field._numberOfStrings, 6);
 128       query.Where.AddEqualTo(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.Field._productCategoryId, productCategory.PrimaryKey[0]);
 129 
 130       // Retrieve the number of strings
 131       query.AddSelectField(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.Field._numberOfStrings.As("NumberOfStrings"));
 132 
 133       /****** Here's where we are Left Outer Joining the Sub Class **********/
 134 
 135       // Always retrieve a body description whether or not the
 136       // row is an instance of 'AcousticGuitar'
 137       query.AddSelectField(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.Field._bodyDescription.As("BodyDescription"));
 138 
 139       // Execute the query
 140       DataSet guitarDataSet = query.FindDataSet();
 141 
 142       // Validate that we found the instance via the query
 143       Assert.AreEqual(1, guitarDataSet.Tables[0].Rows.Count, "Found exactly one row.");
 144       Assert.AreEqual(6, guitarDataSet.Tables[0].Rows[0]["NumberOfStrings"], "The number of strings is 6.");
 145       Assert.AreEqual(DBNull.Value, guitarDataSet.Tables[0].Rows[0]["BodyDescription"], "The body description is null.");
 146 
 147       // From the guitarDataSet query, DomainObjects generates the following SQL SELECT statement.
 148       // Note that there are no concrete type constraints:
 149       //
 150       // SELECT  GUITARS12.NumberOfStrings AS NumberOfStrings,
 151       //         GUITARS12.BodyDesc        AS BodyDescription
 152       // FROM    GUITARS                   AS GUITARS12
 153       // WHERE ((GUITARS12.NumberOfStrings     = @GUITARS12_NumberOfStrings_0
 154       //     AND GUITARS12.PRODUCT_CATEGORY_ID = @GUITARS12_PRODUCT_CATEGORY_ID_1))
 155       //
 156       //  Parameter values:
 157       //  @GUITARS12_NumberOfStrings_0 = 6
 158       //  @GUITARS12_PRODUCT_CATEGORY_ID_1 = 64
 159 
 160 
 161       // Now let's query for the Guitar instance without left outer joining
 162       // i.e., by inner joining the subclass. This will cause DomainObjects to
 163       // add a concrete type constraint to the WHERE clause which ensures that
 164       // only instances of AcousticGuitar or its subclasses are retrieved.
 165       Query<Guitar> query2 = new Query<Guitar>();
 166       query2.Where.AddEqualTo(Types.Guitar.SubClass.AcousticGuitar.Field._numberOfStrings, 6);
 167       query2.Where.AddEqualTo(Types.Guitar.SubClass.AcousticGuitar.Field._productCategoryId, productCategory.PrimaryKey[0]);
 168 
 169       // Retrieve the number of strings
 170       query2.AddSelectField(Types.Guitar.SubClass.AcousticGuitar.Field._numberOfStrings.As("NumberOfStrings"));
 171 
 172       // retrieve a body description if the
 173       // row is an instance of 'AcousticGuitar'
 174       query2.AddSelectField(Types.Guitar.SubClass.AcousticGuitar.Field._bodyDescription.As("BodyDescription"));
 175 
 176       // Execute the query
 177       DataSet acousticGuitarDataSet = query2.FindDataSet();
 178 
 179       // Validate that we did not find any instances of AcousticGuitar or its subclasses
 180       Assert.AreEqual(0, acousticGuitarDataSet.Tables[0].Rows.Count, "Did not find any instances of AcousticGuitar or its subclasses.");
 181 
 182       // From the acousticGuitarDataSet query, DomainObjects generates the following SQL SELECT statement.
 183       // Note that concrete type constraints have been added to the statement:
 184       //
 185       // SELECT  GUITARS12.NumberOfStrings AS NumberOfStrings,
 186       //         GUITARS12.BodyDesc        AS BodyDescription
 187       // FROM    GUITARS                   AS GUITARS12
 188       // WHERE ((GUITARS12.NumberOfStrings     = @GUITARS12_NumberOfStrings_0
 189       //     AND GUITARS12.PRODUCT_CATEGORY_ID = @GUITARS12_PRODUCT_CATEGORY_ID_1)
 190       //     AND ((GUITARS12.ConcreteClass IN ( @GUITARS12_ConcreteClass_2, @GUITARS12_ConcreteClass_3))))
 191       //
 192       // Parameter values:
 193       // @GUITARS12_NumberOfStrings_0 = 6
 194       // @GUITARS12_PRODUCT_CATEGORY_ID_1 = 65
 195       // @GUITARS12_ConcreteClass_2 = 'DomainObjects.Test.Domain.AcousticGuitar'
 196       // @GUITARS12_ConcreteClass_3 = 'DomainObjects.Test.Domain.SpanishAcousticGuitar'
 197     }
 198 
 199     /// <summary>
 200     /// Validates that when a base class left outer joins
 201     /// its sub class, that a subclass type constraint is not applied
 202     /// to the query.
 203     /// </summary>
 204     [Test]
 205     public void LeftOuterJoinSubClass2()
 206     {
 207       // Create an instance of a 'ProductCategory' that
 208       // is related to an instance of 'Guitar'
 209       SpanishProductCategory productCategory = new SpanishProductCategory(GenerateUniqueString(Types.SpanishProductCategory.Field._name), GenerateUniqueString(Types.SpanishProductCategory.Field._description), GenerateUniqueString(Types.SpanishProductCategory.Field._spanishDescription));
 210       string manufacturer = GenerateUniqueString(Types.SpanishAcousticGuitar.Field._manufacturer);
 211       string bodyDescription = GenerateUniqueString(Types.SpanishAcousticGuitar.Field._bodyDescription);
 212       SpanishAcousticGuitar guitar = new SpanishAcousticGuitar(productCategory, manufacturer, 8, bodyDescription);
 213 
 214       // Persist the new category and guitar
 215       PersistenceFacade.Persist(productCategory, guitar);
 216 
 217       // Now find the 6 string guitars associated with the new
 218       // product category we created above. (There should be only one)
 219       Query<Guitar> query = new Query<Guitar>();
 220       query.Where.AddEqualTo(Types.Guitar.Field._manufacturer, manufacturer);
 221       query.Where.AddEqualTo(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.SubClass.SpanishAcousticGuitar.Field._productCategoryId, productCategory.PrimaryKey[0]);
 222 
 223       // Retrieve the number of strings
 224       query.AddSelectField(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.Field._numberOfStrings.As("NumberOfStrings"));
 225 
 226       /****** Here's where we are Left Outer Joining the Sub Class **********/
 227 
 228       // Always retrieve a body description whether or not the
 229       // row is an instance of 'AcousticGuitar'
 230       query.AddSelectField(Types.Guitar.LeftOuterJoinSubClass.AcousticGuitar.LeftOuterJoinSubClass.SpanishAcousticGuitar.Field._bodyDescription.As("BodyDescription"));
 231 
 232       // Execute the query
 233       DataSet guitarDataSet = query.FindDataSet();
 234 
 235       // Validate that we found the instance via the query
 236       Assert.AreEqual(1, guitarDataSet.Tables[0].Rows.Count, "Found exactly one row.");
 237       Assert.AreEqual(8, guitarDataSet.Tables[0].Rows[0]["NumberOfStrings"], "The correct number of strings was retrieved.");
 238       Assert.AreEqual(bodyDescription, guitarDataSet.Tables[0].Rows[0]["BodyDescription"], "The body description was retrieved.");
 239     }
 240 
 241     [Test]
 242     public void ChangeReferenceTo()
 243     {
 244       ProductQuery pathToProduct = Types.Product;
 245 
 246       ColumnField primaryKey = pathToProduct._productCategory.Field._primaryKey;
 247 
 248       // The only check at this point is that an exception is not thrown
 249       ColumnField name = pathToProduct._productCategory.Field._name;
 250     }
 251 
 252     [Test]
 253     public void ChangeReferenceToAndSelectField()
 254     {
 255       ProductCategoryQuery pathToProductCategory = Types.Product._productCategory;
 256 
 257       ColumnField primaryKey = pathToProductCategory._guitarsInThisCategory.Field._primaryKey;
 258 
 259       // The only check at this point is that an exception is not thrown
 260       ColumnField unit = pathToProductCategory._productsInThisCategory.Field._unit;
 261 
 262       Query<Product> productQuery = new Query<Product>();
 263       productQuery.AddSelectField(primaryKey);
 264       productQuery.AddSelectField(unit);
 265 
 266       productQuery.FindDataSet();
 267     }
 268   }
 269 }