簡易ORMフレームワークを作成してみる(6)

AND,OR,NOTの追加

次の目標のシナリオはAND、OR、NOTの論理演算が利用できるようにします。
AND、OR、NOTは構成に要素に他の検索条件すなわちCriteriaを持つようになります。FilterCriteriaの構成要素では単純なパラメータしか表現できなため、これらを表現するためには新しいクラスを追加する必要があります。
いくつかパターンはありますが、個人的な趣味と経験から、AND,ORを1つにまとめて連結演算条件クラスのJoinCriteriaで表現し NOTは単項演算SubCriteria クラスで表現するようにします。

[Test]
public void And()
{
	QueryObject qo = new QueryObject();
	Criteria c1 = qo.Eq("COLUMN1", "ABC");
	Criteria c2 = qo.Eq("COLUMN2", "DEF");
	qo.Filter = qo.And(c1, c2);
	QueryObjectBuilder b = new SqlQueryObjectBuilder(qo);
	Assert.AreEqual(" Where ([COLUMN1] = @p1) And ([COLUMN2] = @p2)", b.QueryText);
	Assert.AreEqual("ABC", b.Parameters[0].Value);
	Assert.AreEqual("DEF", b.Parameters[1].Value);
}

[Test]
public void Or()
{
	QueryObject qo = new QueryObject();
	Criteria c1 = qo.Eq("COLUMN1", "ABC");
	Criteria c2 = qo.Eq("COLUMN2", "DEF");
	qo.Filter = qo.Or(c1, c2);
	QueryObjectBuilder b = new SqlQueryObjectBuilder(qo);
	Assert.AreEqual(" Where ([COLUMN1] = @p1) Or ([COLUMN2] = @p2)", b.QueryText);
	Assert.AreEqual("ABC", b.Parameters[0].Value);
	Assert.AreEqual("DEF", b.Parameters[1].Value);
}

[Test]
public void Not()
{
	QueryObject qo = new QueryObject();
	Criteria c1 = qo.Eq("COLUMN1", "ABC");
	qo.Filter = qo.Not(c1);
	QueryObjectBuilder b = new SqlQueryObjectBuilder(qo);
	Assert.AreEqual(" Where Not ([COLUMN1] = @p1)", b.QueryText);
	Assert.AreEqual("ABC", b.Parameters[0].Value);
}


連結演算条件クラスのJoinCriteriaは、構成要素としては任意個の他の検索条件と連結オペレータが指定できればOKで、構成される検索条件をANDやORなどの連結オペレータで結合すればよいだけです。
実際の検索条件式の生成はGenerateQueryStatementで行われます。このメソッドが呼び出された段階ではQueryObjectBuilder が提供されるので、生成された具体的なSQL文を利用することができます。

[Serializable]
public class JoinCriteria : Criteria
{
	private string _operator;
	public string Operator
	{
		get { return _operator; }
		set { _operator = value; }
	}

	private Criteria _items;
	public Criteria Items
	{
		get { return _items; }
		set { _items = value; }
	}

	public JoinCriteria()
	{
	}

	public JoinCriteria(string ope, Criteria item)
	{
		this.Operator = ope;
		this.Items = (new Criteria { item });
	}

	public JoinCriteria(string ope, params Criteria items)
	{
		this.Operator = ope;
		this.Items = items;
	}

	public override string GenerateQueryStatement(QueryObjectBuilder builder)
	{
		List items = new List();
		foreach (Criteria conjItem in Items)
		{
			if (conjItem != null)
			{
				string itemStat = conjItem.GenerateQueryStatement(builder);
				if (itemStat != string.Empty)
				{
					items.Add("(" + itemStat + ")");
				}
			}
		}
		return string.Join(" " + Operator + " ", items.ToArray());
	}
}

次に単項演算SubCriteriaクラスに取り掛かります。作成指針は、サブの検索条件に対して、任意のオペレータとしてのテンプレート式を適用できるようにします。たとえば、サブの検索条件「Xyz=@p1」に テンプレート式「Not({0})」を適用することで、検索条件式「Not(Xyz=@p1)」を生成できるようにします。なお、サブ条件が空の場合、別の式が生成できるようにしておくと便利なので追加しておきます。

[Serializable]
public class SubCriteria : Criteria
{
	public SubCriteria()
	{
	}

	public SubCriteria(string expression, string nullExpression, Criteria item)
	{
		this.Expression = expression;
		this.NullExpression = nullExpression;
		this.Item = item;
	}

	private string _expression;
	public string Expression
	{
		get { return _expression; }
		set { _expression = value; }
	}
	private string _nullExpression;
	public string NullExpression
	{
		get { return _nullExpression; }
		set { _nullExpression = value; }
	}

	private Criteria _item;
	public Criteria Item
	{
		get { return _item; }
		set { _item = value; }
	}

	public override string  GenerateQueryStatement(QueryObjectBuilder builder)
	{
		if (Item == null) return NullExpression;
		string stat = Item.GenerateQueryStatement(builder);
		if (stat.Trim().Length == 0) return NullExpression;
		return string.Format(Expression, stat);
	}

}

最後にQueryObjectにファクトリクラスを追加して完成です。

[Serializable]
public class QueryObject
{
	...
	public JoinCriteria And(params Criteria items)
	{
		return new JoinCriteria("And", items);
	}
	public JoinCriteria Or(params Criteria items)
	{
		return new JoinCriteria("Or", items);
	}
	public SubCriteria Not(Criteria item)
	{
		return new SubCriteria("Not ({0})",string.Empty,item);
	}
}