IE 2016

Code First и Linq to EF на примере 1С версии 7.7 и 8.3 часть I

Опубликовал Serginio в раздел Программирование - Практика программирования

Данный проект  является чисто исследовательским примером использования Code First и Linq to EF на примере 1С версии 7.7. Так как сам я программист 1С, то мне всегда было интересно, как можно перенести модель объектов 1С на компилируемые языки, и использовать мощь Linq to EF.
С появлением Code First давно хотел прикрутить, но все как-то руки не доходили, и вот, наконец ..

Продолжение здесь http://infostart.ru/public/402038/ 

Здесь лежит практика использования http://infostart.ru/public/402433/


И примеры использования классов .Net в 1С

http://infostart.ru/public/448668/

http://infostart.ru/public/238584/

Для начала создадим базовый класс

 

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 = newModel1())

            {

              

                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 бдпр_ДляПериодических

                            selectnew

                            {

                                Наименование=спраименование,

                                ДатаСпр=спратаСпр,

                                Периодические=(from прериод in  query.Where(х=> х.OBJID==спр.ID && х.DATE<=спратаСпр).Take(1)

                                                   selectnew

                                                   {

                                                      Значение=прериод.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]

        publicdecimal  ПериодЧисло

        {

            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)

                    returnnull;

 

 

                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; }";
    КонецЕсли;
КонецФункции // гл


Экспериментируйте не на рабочей базе!!!

Буду рад, если кому-то это поможет

 

Файлы

Наименование Файл Версия Размер Кол. Скачив.
Обработки формирующая тексты модулей для Code First для баз 7.7 и 8.3
.zip 119,53Kb
25.09.15
2
.zip 119,53Kb 2 Скачать

См. также

Комментарии

1. Serginio 31.08.2015 14:31
Вот какие левые запросы с условием на неравенство получились

var бд = Константы1С.ГлобальныйКонтекст.БД;

            var qr = from Номенклатура in бд.Спр_Номенклатура 
                     from единицы in бд.Спр_Единицы.Where(единица => единица.ВладелецId == Номенклатура.ID && единица.ШтрихКод.CompareTo("4") > 0).DefaultIfEmpty() 
                     select new { Номенклатура.Наименование,
                         Номенклатура.ПолнНаименование,
                         единицы.ШтрихКод,
                         ОКЕИ=единицы.ОКЕИ.Наименование
                     };



            foreach (var элем in qr.Take(1000)) 
            { 

                if (элем.ШтрихКод == null)
                    continue;

                Console.WriteLine("{0}.{1} - {2}", элем.Наименование.TrimEnd(), элем.ШтрихКод == null ? "null" : элем.ШтрихКод, элем.ПолнНаименование.TrimEnd(), элем.ОКЕИ == null ? "null" : элем.ОКЕИ);


            }

...Показать Скрыть


Выдает такой запрос
SELECT 
    [Limit1].[C1] AS [C1], 
    [Limit1].[DESCR] AS [DESCR], 
    [Limit1].[SP101] AS [SP101], 
    [Limit1].[SP80] AS [SP80], 
    [Limit1].[DESCR1] AS [DESCR1]
    FROM ( SELECT TOP (1000) 
        [Extent1].[DESCR] AS [DESCR], 
        [Extent1].[SP101] AS [SP101], 
        [Extent2].[SP80] AS [SP80], 
        [Extent3].[DESCR] AS [DESCR1], 
        1 AS [C1]
        FROM   [dbo].[SC84] AS [Extent1]

        LEFT OUTER JOIN [dbo].[SC75] AS [Extent2] ON ([Extent2].[PARENTEXT] = [Extent1].[ID]) AND ([Extent2].[SP80] > N'4') 
        LEFT OUTER JOIN [dbo].[SC41] AS [Extent3] ON [Extent2].[SP79] = [Extent3].[ID] 
    )  AS [Limit1]


...Показать Скрыть
Или больше мне нравится таой запрос

var бд = Константы1С.ГлобальныйКонтекст.БД;
            var qr = from Номенклатура in бд.Спр_Номенклатура

                     select new
                     {
                         Номенклатура.Наименование,
                         Номенклатура.ПолнНаименование,
                         Единицы = (Номенклатура.ПодчиненныеЕдиницы.Where(единица => единица.ШтрихКод.CompareTo("4") > 0)
                         .Select(единица =>

                         new
                         {
                             единица.ШтрихКод,
                             ОКЕИ = единица.ОКЕИ.Наименование
                         }
            )
                         )
                     };


            foreach (var элем in qr.Take(1000))
            {


                foreach (var единица in элем.Единицы)
                    Console.WriteLine("{0}.{1} - {2}", элем.Наименование.TrimEnd(), единица.ШтрихКод, элем.ПолнНаименование.TrimEnd(), единица.ОКЕИ);


            }

...Показать Скрыть

Генерирут SQL запрос


SELECT 



    [Project2].[ID] AS [ID], 
    [Project2].[C1] AS [C1], 
    [Project2].[DESCR] AS [DESCR], 
    [Project2].[SP101] AS [SP101], 
    [Project2].[C2] AS [C2], 
    [Project2].[SP80] AS [SP80], 
    [Project2].[DESCR1] AS [DESCR1]
    FROM ( SELECT 
        [Limit1].[ID] AS [ID], 
        [Limit1].[DESCR] AS [DESCR], 
        [Limit1].[SP101] AS [SP101], 
        [Limit1].[C1] AS [C1], 
        [Join1].[SP80] AS [SP80], 
        [Join1].[DESCR] AS [DESCR1], 
        CASE WHEN ([Join1].[PARENTEXT] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
        FROM   (SELECT TOP (1000) 
            [Extent1].[ID] AS [ID], 
            [Extent1].[DESCR] AS [DESCR], 
            [Extent1].[SP101] AS [SP101], 
            1 AS [C1]
            FROM [dbo].[SC84] AS [Extent1] ) AS [Limit1]
        LEFT OUTER JOIN  (SELECT [Extent2].[PARENTEXT] AS [PARENTEXT], [Extent2].[SP80] AS [SP80], [Extent3].[DESCR] AS [DESCR]
            FROM  [dbo].[SC75] AS [Extent2]
            INNER JOIN [dbo].[SC41] AS [Extent3] ON [Extent2].[SP79] = [Extent3].[ID] ) AS [Join1] ON ([Limit1].[ID] = [Join1].[PARENTEXT]) AND ([Join1].[SP80] > N'4')
    )  AS [Project2]
    ORDER BY [Project2].[ID] ASC, [Project2].[C2] ASC
...Показать Скрыть
# Ответить
Внимание! За постинг в данном форуме $m не начисляются.
Внимание! Для написания сообщения необходимо авторизоваться
Текст сообщения*
Прикрепить файл






IE 2016