Продолжение здесь //infostart.ru/public/402038/
Здесь лежит практика использования //infostart.ru/public/402433/
И примеры использования классов .Net в 1С
Для начала создадим базовый класс
public class СправочникПредок
{
public virtual string ID { get; set; }
public virtual string Наименование { get; set; }
public virtual bool ПометкаУдаления { get; set; }
public virtual byte ISFOLDER { get; set; }
public virtual object ПолучитьКод() { return""; }
public virtual string Вид() { return""; }
public virtual bool ЭтоГруппа() { return ISFOLDER == 1; }
}
Зачем это нужно, поясню чуть позже.
На его основе можно создать описание справочника, например, Номенклатуры
[Table("SC84")]
public partial class Номенклатура :СправочникПредок
{
public DateTime ДатаДляПериодическихРеквизитов = DateTime.Now;
[Key]
[Required]
[StringLength(9)]
override public string ID { get; set; }
[Column("CODE")]
[Required]
[StringLength(8)]
public string Код { get; set; }
[Column("DESCR")]
[Required]
[StringLength(99)]
override public string Наименование { get; set; }
[Column("PARENTID")]
[Required]
[StringLength(9)]
public string РодительId { get; set; }
override public byte ISFOLDER { get; set; }
[Column("ISMARK")]
override public bool ПометкаУдаления { get; set; }
..............................................................
[Column("SP94")]
[Required]
[StringLength(9)]
public string ОсновнаяЕдиницаId { get; set; }
[Column("SP8906")]
[Required]
[StringLength(9)]
public string МатериалId { get; set; }
..............................................................
public override object ПолучитьКод() { return Код; }
virtual public Справочник.Номенклатура Родитель { get; set; }
virtual public Справочник.Номенклатура Материал { get; set; }
[InverseProperty("Материал")]
public ICollection<Справочник.Номенклатура> ПодчиненныеДляМатериал { get; set; }
[InverseProperty("Владелец")]
public ICollection<Справочник.Единицы> ПодчиненныеЕдиницы { get; set; }
override public string Вид() { returnВидыСправочников.Номенклатура; }
Через атрибуты мы помечаем поля так, как нам нужно, при этом создаем свойства нужного нам типа.
Про соглашение имен подробно написано здесь
http://metanit.com/sharp/entityframework/2.6.php
Дополнительно обращу ваше внимание на
virtual public Справочник.Номенклатура Материал { get; set; }
[InverseProperty("Материал")]
public ICollection<Справочник.Номенклатура> ПодчиненныеДляМатериал { get; set; }
Это требует Code First для разрешения ссылок, когда один тип ссылается на сам себя и описания один ко многим. Также нужно описать коллекции на подчинённые справочники
public partial class Единицы :СправочникПредок
{
public DateTime ДатаДляПериодическихРеквизитов = DateTime.Now;
[Key]
[Required]
[StringLength(9)]
override public string ID { get; set; }
[NotMapped]
override public string Наименование { get { return""; } set { } }
[Column("PARENTEXT")]
[Required]
[StringLength(9)]
public string ВладелецId { get; set; }
[NotMapped]
override public byte ISFOLDER { get { return 2; } set { } }
[Column("ISMARK")]
override public bool ПометкаУдаления { get; set; }
[Column("SP79")]
[Required]
[StringLength(9)]
public string ОКЕИId { get; set; }
[Column("SP76", TypeName = "numeric")]
public decimal Вес { get; set; }
[Column("SP78", TypeName = "numeric")]
publicdecimal Коэффициент { get; set; }
[Column("SP80")]
[Required]
[StringLength(13)]
public string ШтрихКод { get; set; }
[Column("SP8752", TypeName = "numeric")]
public decimal Объем { get; set; }
[Column("SP9519")]
[Required]
[StringLength(36)]
public string Ref { get; set; }
virtual public Справочник.Номенклатура Владелец { get; set; }
virtual public Справочник.ОКЕИ ОКЕИ { get; set; }
override public string Вид() { return ВидыСправочников.Единицы; }
}
У справочника единицы нет ни Наименования, ни кода и поля Родитель. Пометим эти свойства как [NotMapped] и
установим возвращаемые значения по умолчанию
На этом описательная часть закончена, перейдём к самому вкусному.
Используя предка, можно написать обобщенную функцию.
public TEntity ПолучитьЭлементСправочника<TEntity>(string ID) where TEntity : СправочникПредок
{
var query2 = from спр in this.Set<TEntity>()
where спр.ID == ID
select спр;
return query2.SingleOrDefault<TEntity>();
}
Теперь мы можем вызвать её таким способом
public static СправочникПредок ПолучитьЗначениеНеопределенногоСправочника(string НеопределенныйИД)
{
var тип = НеопределенныйИД.Substring(0, 4);
var id = НеопределенныйИД.Substring(4, 9);
switch (тип)
{
case ВыдСправочника36.Аналоги:
return БД.ПолучитьЭлементСправочника<Справочник.Аналоги>(id);
case ВыдСправочника36.Банки:
return БД.ПолучитьЭлементСправочника<Справочник.Банки>(id);
При получении неопределенного справочника нам доступны все свойства и методы предка.
Следует отметить, что Linq возвращает не реальный объект, а прокси. То есть для того, что бы получить реальный тип нужно вызвать GetType().BaseType
А теперь посмотри на мощь LINQ.
В Linq есть два синтаксиса запросов, которые можно совмещать
using (var db = new Model1())
{
var Товары = db.Спр_Номенклатура.Include("ПодчиненныеЕдиницы").Where(p => p.ISFOLDER == 2 && p.РодительId != " 0 " && p.ПодчиненныеЕдиницы.Any()).Take(10);
foreach (var Товар in Товары)
{
Console.WriteLine("{0}.{1} - {2}", Товар.ID, Товар.Наименование, Товар.Код);
foreach (var Единица in Товар.ПодчиненныеЕдиницы)
Console.WriteLine("{0}.{1} - {2}", Единица.ОКЕИ.Наименование, Единица.ШтрихКод, Единица.Коэффициент);
}
}
Такой запрос генерирует запрос к базе. Не буду указывать все поля, укажу только условия
) AS [Project1]
WHERE (2 = [Project1].[ISFOLDER]) AND (N' 0 ' <> [Project1].[PARENTID]) AND ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[SC75] AS [Extent2]
WHERE [Project1].[ID] = [Extent2].[PARENTEXT]
)) ) AS [Limit1]
LEFT OUTER JOIN [dbo].[SC75] AS [Extent3] ON [Limit1].[ID] = [Extent3].[PARENTEXT]
Так как по умолчанию применяется ленивая загрузка, то для получения подчиненных единиц генерируется отдельный запрос при первом обращении
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[CODE] AS [CODE],
[Extent1].[DESCR] AS [DESCR],
[Extent1].[ISMARK] AS [ISMARK],
[Extent1].[SP42] AS [SP42],
[Extent1].[SP9525] AS [SP9525]
FROM [dbo].[SC41] AS [Extent1]
WHERE [Extent1].[ID] = @EntityKeyValue1
-- EntityKeyValue1: ' 1 ' (Type = String, IsNullable = false, Size = 9)
Перейдем к более сложным коррелирующим запросам
var бд = Константы1С.ГлобальныйКонтекст.БД;
var query = from Константа in бд.ТаблицаКонстанты
where Константа.ID == 9697
orderby Константа.DATE descending, Константа.TIME descending, Константа.DOCID descending, Константа.ROW_ID descending
select Константа;
// select new Константы1С.ЗначениеПериодического { Значение = Константа.VALUE }).Take(1);
var query2= (from спр in бд.Спр_ДляПериодических
select new
{
Наименование=спр.Наименование,
ДатаСпр=спр.ДатаСпр,
Периодические=(from прериод in query.Where(х=> х.OBJID==спр.ID && х.DATE<=спр.ДатаСпр).Take(1)
select new
{
Значение=прериод.VALUE,
Дата=прериод.DATE
}).FirstOrDefault()
}
);
foreach (var элем in query2)
{
Console.WriteLine("{0}.{1} - {2}", элем.Наименование, элем.ДатаСпр, элем.Периодические);
}
Здесь мечта каждого программиста на 8 ке и 7 ке выполняет следующий код
SELECT
1 AS [C1],
[Extent1].[DESCR] AS [DESCR],
[Extent1].[SP9700] AS [SP9700],
[Limit1].[ROW_ID] AS [ROW_ID],
[Limit1].[VALUE] AS [VALUE],
[Limit1].[DATE] AS [DATE]
FROM [dbo].[SC9691] AS [Extent1]
OUTER APPLY (SELECT TOP (1) [Project1].[ROW_ID] AS [ROW_ID], [Project1].[DATE] AS [DATE], [Project1].[VALUE] AS [VALUE]
FROM ( SELECT
[Extent2].[ROW_ID] AS [ROW_ID],
[Extent2].[DATE] AS [DATE],
[Extent2].[VALUE] AS [VALUE],
[Extent2].[DOCID] AS [DOCID],
[Extent2].[TIME] AS [TIME]
FROM [dbo].[_1SCONST] AS [Extent2]
WHERE (9697 = [Extent2].[ID]) AND ([Extent2].[OBJID] = [Extent1].[ID]) AND ([Extent2].[DATE] <= [Extent1].[SP9700])
) AS [Project1]
ORDER BY [Project1].[DATE] DESC, [Project1].[TIME] DESC, [Project1].[DOCID] DESC, [Project1].[ROW_ID] DESC ) AS [Limit1]
Следует заметить, что если не использовать FirstOrDefault()
То в Периодические будет коллекция.
Можно применять такие запросы
var бд = Константы1С.ГлобальныйКонтекст.БД;
var рез= ( from спр in бд.Спр_Номенклатура
where спр.ВидНоменклатуры==Перечисление.ВидыНоменклатуры.Услуга
select спр).Take(10);
Для тестов с периодическими реквизитами создал тестовый справочник.
Доступ к ним такой.
[NotMapped]
public DateTime ПериодДата
{
get
{
return Константы1С.Константы.ДатаДляПериодического(9693, ID, ДатаДляПериодическихРеквизитов);
}
}
[NotMapped]
public decimal ПериодЧисло
{
get
{
var query = Константы1С.Константы.ЗапросДляПериодическогоЗначения(9694, ID, ДатаДляПериодическихРеквизитов);
var res = query.SingleOrDefault();
if (res == null)
return 0M;
return decimal.Parse(res.Значение, System.Globalization.CultureInfo.InvariantCulture);
}
}
[NotMapped]
public Int64 ПериодИнт
{
get
{
var query = Константы1С.Константы.ЗапросДляПериодическогоЗначения(9695, ID, ДатаДляПериодическихРеквизитов);
var res = query.SingleOrDefault();
if (res == null)
return 0;
returnInt64.Parse(res.Значение, System.Globalization.CultureInfo.InvariantCulture);
}
}
[NotMapped]
public Справочник.Номенклатура ПериодСПР
{
get
{
var query = Константы1С.Константы.ЗапросДляПериодическогоЗначения(9696, ID, ДатаДляПериодическихРеквизитов);
var db = Константы1С.ГлобальныйКонтекст.БД;
var query2 = from спр in db.Спр_Номенклатура
join конст in query on спр.ID equals конст.Значение.Substring(0, 9)
selectспр;
return query2.SingleOrDefault();
}
}
[NotMapped]
public string ПериодСтр
{
get
{
var query = Константы1С.Константы.ЗапросДляПериодическогоЗначения(9697, ID, ДатаДляПериодическихРеквизитов);
var res = query.SingleOrDefault();
if (res == null)
return null;
return res.Значение.Substring(0, 10);
}
}
}
И соответственно сам запрос к периодически данным
publicstatic System.Linq.IQueryable<Константы1С.ЗначениеПериодического> ЗапросДляПериодическогоЗначения(int ID, string OBJID, DateTime DATE)
{
var бд = ГлобальныйКонтекст.БД;
var query = (from Константа in бд.ТаблицаКонстанты
where Константа.ID == ID && Константа.OBJID == OBJID && Константа.DATE <= DATE
orderby Константа.DATE descending, Константа.TIME descending, Константа.DOCID descending, Константа.ROW_ID descending
select new Константы1С.ЗначениеПериодического{ Значение = Константа.VALUE }).Take(1);
return query;
}
Основной метод формирующий описания выглядит так
//Пропишем тип реквизита и виртуальные свойства //В зависимости от типа поля
Функция ПолучитьТипРеквизита(вал,Стр,СтрВиртуал,IDПоля,ВидСпр="",СтрКонструктор)
ИмяПоля=Вал.Идентификатор;
TypeName="";
ИмяРеквизита=ИмяПоля;
тип=Вал.Тип;
резулт="";
Required=1;
ДобавитьКолонкуДляНеопределенногоТипа=0;
Если Тип="Строка" Тогда
резулт="string";
ДлинаНаименования=вал.длина;
Если ДлинаНаименования=0 Тогда
TypeName = "text";
Required=0;
Иначе
стр="[StringLength("+ДлинаНаименования+")]"
КонецЕсли;
ИначеЕсли Тип="Справочник" Тогда
резулт="string";
ИмяРеквизита=ИмяПоля+"Id";
Вид=СокрЛП(Вал.Вид);
Если Вид="" Тогда
// Это неопределенный справочник
// Установим полю длину 13
// И добавим свойство которого нет в базе данных
//Будем сами его извлекать из свойства
стр="[StringLength(13)]";
СтрВиртуал="
| [NotMapped]
|public СправочникПредок "+ИмяПоля+"
|{
| get
| {
| return ГлобальныйКонтекст.ПолучитьЗначениеНеопределенногоСправочника("+ИмяРеквизита+");
| }
|}";
Иначе
// Это определенный справочник и Code First сама сможет загружать и сохранять такие свойства
// Установим длину поля 9 символов
стр="[StringLength(9)]";
резулт="string";
СтрВиртуал=СтрВиртуал+"
|virtual public Справочник."+Вид+" "+ИмяПоля+" { get; set; }";
// Когда вид реквизита видом самого справочника добавим коллекцию на подчиненные элементы
Если Вид=ВидСпр Тогда
СтрВиртуал=СтрВиртуал+"
|[InverseProperty("""+ИмяПоля+""")]
|public ICollection<Справочник."+Вид+"> ПодчиненныеДля"+ИмяПоля+" { get; set; }";
СтрКонструктор=СтрКонструктор+"
|ПодчиненныеДля"+ИмяПоля+" = new List<Справочник."+Вид+">();";
КонецЕсли;
КонецЕсли;
ИначеЕсли Тип="Документ" Тогда
резулт="string";
ИмяРеквизита=ИмяПоля+"Id";
Вид=СокрЛП(Вал.Вид);
Если Вид="" Тогда
стр="[StringLength(13)]";
//СтрВиртуал="
//|[NotMapped]
//|public object "+ИмяПоля+" { get{return 1С.КонстантыИПеречисления.ПолучитьЗначениеНеопределенногоДокумента("+ИмяРеквизита+");} }"
Иначе
стр="[StringLength(9)]";
резулт="string";
//Пока не реализован доступ к документам
//СтрВиртуал=СтрВиртуал+"
//|virtual public Документ."+Вид+" "+ИмяПоля+" { get; set; }"
КонецЕсли;
ИначеЕсли Тип="Дата" Тогда
резулт="DateTime"
ИначеЕсли Тип="Число" Тогда
// Числа у нас хранятся в виде numeric
// В зависимости от длины и дробной части можно выбрать
//нужный тип
TypeName = "numeric";
Длина=вал.Длина;
РазрядностьДробнойЧасти=вал.Точность;
резулт=ПолучитьТипЧисловогоПоля(Длина,РазрядностьДробнойЧасти)
ИначеЕсли Тип="Перечисление" Тогда
стр="[StringLength(9)] //Перечисление";
резулт="string";
//Вид=СокрЛП(Вал.Вид);
Иначе
стр="[StringLength(23)] // Неопределенный тип";
резулт="string";
ДобавитьКолонкуДляНеопределенногоТипа=1;
КонецЕсли;
Если Required=1 Тогда
Стр="[Required]
|"+стр;
КонецЕсли;
Если TypeName="" Тогда
Стр="
|[Column("""+IDПоля+""")]
|"+Стр;
Иначе
Стр="
|[Column("""+IDПоля+""",TypeName = """+TypeName+""")]"
КонецЕсли;
Стр=Стр+"
|public "+резулт+" "+ИмяРеквизита+ " { get; set; }";
Если ДобавитьКолонкуДляНеопределенногоТипа=1 Тогда
Стр=Стр+"
| [Column(""T"+IDПоля+""")]
|[Required]
|[StringLength(3)]
|public string Тип"+ИмяПоля+" { get; set; }";
КонецЕсли;
КонецФункции // гл
Экспериментируйте не на рабочей базе!!!
Буду рад, если кому-то это поможет