Попробуйте следующее:
Определение таблицы:
DROP TABLE IF EXISTS category;
CREATE TABLE category (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20),
parent_id INT,
CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
REFERENCES category (id)
) engine=innodb;
Экспериментальные строки:
INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);
Рекурсивная хранимая процедура:
DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
DECLARE catname VARCHAR(20);
DECLARE temppath TEXT;
DECLARE tempparent INT;
SET max_sp_recursion_depth = 255;
SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
IF tempparent IS NULL
THEN
SET path = catname;
ELSE
CALL getpath(tempparent, temppath);
SET path = CONCAT(temppath, '/', catname);
END IF;
END$$
DELIMITER ;
Функция обертки для хранимой процедуры:
DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
DECLARE res TEXT;
CALL getpath(cat_id, res);
RETURN res;
END$$
DELIMITER ;
Выберите пример:
SELECT id, name, getpath(id) AS path FROM category;
Выход:
+----+-----------+-----------------------------------------+
| id | name | path |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1 |
| 20 | category2 | category1/category2 |
| 21 | category3 | category1/category2/category3 |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA |
| 24 | categoryB | category1/categoryA/categoryB |
| 25 | categoryC | category1/categoryA/categoryC |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+
Фильтрация строк определенным путем :
SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';
Выход:
+----+-----------+-----------------------------------------+
| id | name | path |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2 |
| 21 | category3 | category1/category2/category3 |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+
Лично я не знаю каких-либо сторонних или предыдущих решений этой проблемы, поэтому приношу свои извинения, если я покрываю старые позиции. Но если бы я собирался реализовать какой-то стандарт неизменности для проекта, над которым я работал, я бы начал с чего-то вроде этого:
public interface ISnaphot<T>
{
T TakeSnapshot();
}
public class Immutable<T> where T : ISnaphot<T>
{
private readonly T _item;
public T Copy { get { return _item.TakeSnapshot(); } }
public Immutable(T item)
{
_item = item.TakeSnapshot();
}
}
Этот интерфейс был бы реализован примерно так:
public class Customer : ISnaphot<Customer>
{
public string Name { get; set; }
private List<string> _creditCardNumbers = new List<string>();
public List<string> CreditCardNumbers { get { return _creditCardNumbers; } set { _creditCardNumbers = value; } }
public Customer TakeSnapshot()
{
return new Customer() { Name = this.Name, CreditCardNumbers = new List<string>(this.CreditCardNumbers) };
}
}
И клиентский код будет выглядеть примерно так:
public void Example()
{
var myCustomer = new Customer() { Name = "Erik";}
var myImmutableCustomer = new Immutable<Customer>(myCustomer);
myCustomer.Name = null;
myCustomer.CreditCardNumbers = null;
//These guys do not throw exceptions
Console.WriteLine(myImmutableCustomer.Copy.Name.Length);
Console.WriteLine("Credit card count: " + myImmutableCustomer.Copy.CreditCardNumbers.Count);
}
Очевидный недостаток заключается в том, что реализация хороша только как клиент реализации ISnapshot
TakeSnapshot
, но на по крайней мере, это будет стандартизировать вещи, и вы будете знать, где искать, если у вас есть проблемы, связанные с сомнительной изменчивостью. Бремя также будет лежать на потенциальных разработчиках, чтобы понять, могут ли они обеспечить неизменность снимка и не реализовать интерфейс, если нет (то есть класс возвращает ссылку на поле, которое не поддерживает какой-либо вид клонирования / копирования и, следовательно, не может быть снимок-е изд.)
Я понимаю, что это не так уж далеко от простой реализации объектной копии, но она стандартизирует копию с точки зрения неизменности. В кодовой базе вы можете увидеть некоторых разработчиков из ICloneable
, некоторых конструкторов копирования и некоторые явные методы копирования, возможно, даже в одном классе. Определив что-то вроде этого, вы узнаете, что намерение определенно связано с неизменяемостью - я хочу сделать снимок, а не дублирующий объект, потому что мне нужно больше и этого объекта. Класс Immtuable<T>
также централизует связь между неизменяемостью и копиями; если позже вы захотите как-то оптимизировать, например, кэшировать снимок до «грязного», вам не нужно делать это во всех разработчиках логики копирования.
Используйте этот шаблон T4 , который я собрал, чтобы решить эту проблему. Как правило, он должен соответствовать вашим потребностям в любых видах неизменных объектов, которые вам нужно создать.
Нет необходимости использовать дженерики или использовать какие-либо интерфейсы. Для моих целей я не хочу, чтобы мои неизменяемые классы были конвертируемыми друг в друга. Почему ты? Какие общие черты они должны разделить, что означает, что они должны быть обратимы друг к другу? Применение шаблона кода должно быть задачей генератора кода (или, что еще лучше, достаточно удобной системы типов, позволяющей вам определять общие шаблоны кода, которых, к сожалению, в C # нет).
Вот некоторые примеры выходных данных из шаблона, чтобы проиллюстрировать базовую концепцию в игре (не говоря уже о типах, используемых для свойств):
public sealed partial class CommitPartial
{
public CommitID ID { get; private set; }
public TreeID TreeID { get; private set; }
public string Committer { get; private set; }
public DateTimeOffset DateCommitted { get; private set; }
public string Message { get; private set; }
public CommitPartial(Builder b)
{
this.ID = b.ID;
this.TreeID = b.TreeID;
this.Committer = b.Committer;
this.DateCommitted = b.DateCommitted;
this.Message = b.Message;
}
public sealed class Builder
{
public CommitID ID { get; set; }
public TreeID TreeID { get; set; }
public string Committer { get; set; }
public DateTimeOffset DateCommitted { get; set; }
public string Message { get; set; }
public Builder() { }
public Builder(CommitPartial imm)
{
this.ID = imm.ID;
this.TreeID = imm.TreeID;
this.Committer = imm.Committer;
this.DateCommitted = imm.DateCommitted;
this.Message = imm.Message;
}
public Builder(
CommitID pID
,TreeID pTreeID
,string pCommitter
,DateTimeOffset pDateCommitted
,string pMessage
)
{
this.ID = pID;
this.TreeID = pTreeID;
this.Committer = pCommitter;
this.DateCommitted = pDateCommitted;
this.Message = pMessage;
}
}
public static implicit operator CommitPartial(Builder b)
{
return new CommitPartial(b);
}
}
Базовая схема состоит в том, чтобы иметь неизменный класс с вложенным изменяемым Builder
класс, который используется для создания экземпляров неизменяемого класса изменяемым образом. Единственный способ установить свойства неизменяемого класса - это создать класс ImmutableType.Builder
и установить его обычным изменяемым способом и преобразовать его в содержащий его класс ImmutableType
с помощью оператора неявного преобразования.
Вы можете расширить шаблон T4, добавив общедоступный ctor по умолчанию к самому классу ImmutableType
, чтобы избежать двойного распределения, если вы можете установить все свойства заранее.
Вот пример использования:
CommitPartial cp = new CommitPartial.Builder() { Message = "Hello", OtherFields = value, ... };
или ...
CommitPartial.Builder cpb = new CommitPartial.Builder();
cpb.Message = "Hello";
...
// using the implicit conversion operator:
CommitPartial cp = cpb;
// alternatively, using an explicit cast to invoke the conversion operator:
CommitPartial cp = (CommitPartial)cpb;
Обратите внимание, что в присваивании используется оператор неявного преобразования из CommitPartial.Builder
в CommitPartial
. Это та часть, которая «замораживает» изменяемый CommitPartial.Builder
путем создания из него нового неизменяемого экземпляра CommitPartial
с нормальной семантикой копирования.
Лучшим решением было бы просто использовать F # там, где вы хотите истинную неизменность?