Как получить уведомления об изменении свойств с помощью генератора EF 4.x DbContext - программирование

Как получить уведомления об изменении свойств с помощью генератора EF 4.x DbContext

Я играю с Entity Framework 4.3, поэтому я использую генератор DbContext для создания классов контекста и сущности.

С шаблоном генератора кода EF 4 по умолчанию, классы объектов реализуют INotifyPropertyChanged, а также добавляют частичные методы Changing и Changed в средствах настройки свойств.

Когда я использую генератор EF 4.x DbContext, как показано на рисунке ниже, классы сущностей намного легче и не включают никаких средств отслеживания изменений свойств.

enter image description here

Вот пример:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;

namespace SomeNamespace
{
    public partial class SomeTable
    {
        public SomeTable()
        {
            this.Children = new HashSet<Child>();
        }

        public long parent_id { get; set; }
        public long id { get; set; }
        public string filename { get; set; }
        public byte[] file_blob { get; set; }

        public virtual Parent Parent { get; set; }
        public virtual ICollection<Child> Children { get; set; }
    }
}

Мне не хватает важной части головоломки, но мои поиски были бесплодны. Поэтому мой вопрос: как я могу сгенерировать типы включенных уведомлений об изменении свойств с помощью EF 4.3?

Edit

Я полностью согласен с ответом @derape; но мне любопытно, почему мне нужно будет изменить файл .tt, когда в шаблоне генерации кода EF 4 уже есть крючки. Я имею в виду, что связано с привязкой к WPF DependencyProperty '? Без INotifyPropertyChanged изменения, внесенные командой в кучу свойств в связке объектов, не будут отображаться в пользовательском интерфейсе. Что мне не хватает?

4b9b3361

Ответ 1

Недавно я наткнулся на эту проблему, я отредактировал свой Entity.tt, чтобы реализовать следующие изменения, быстрый патч, но отлично работает.

Добавить следующее в класс CodeStringGenerator

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3} : {4}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),
        "INotifyPropertyChanged");
}


public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}{6} {4}{5} }}",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
        "set { _"+_code.Escape(edmProperty).ToLower()+" = value; OnPropertyChanged(\""+_code.Escape(edmProperty)+"\");}",
        "get { return _"+_code.Escape(edmProperty).ToLower()+"; }");

}
public string Private(EdmProperty edmProperty) {
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} _{2};",
        "private",
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty).ToLower());

}

Добавить следующее в генератор

using System.ComponentModel;
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
var complexProperties = typeMapper.GetComplexProperties(entity);
#>

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

И нажмите ниже

foreach (var edmProperty in simpleProperties)
{
#>
<#=codeStringGenerator.Private(edmProperty)#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
}


foreach(var complexProperty in complexProperties)
{
#>
<#=codeStringGenerator.Private(complexProperty)#>
    <#=codeStringGenerator.Property(complexProperty)#>
<#
}

Ответ 2

Я создал вариант ответа Андерса со следующими отличиями:

  • Меньше изменений в файле Entity.tt
  • Использование базового класса для реализации INotifyPropertyChanged (удобно для введения других общих функций)
  • Макет чистого кода в классах сгенерированных моделей

Итак, мои шаги:

Создайте базовый класс для расширения классов моделей:

public abstract class BaseModel : INotifyPropertyChanged
{
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Спасибо Хуан Пайбл Гомес за предлагаемое улучшение! Мне нравится ваше улучшение, даже если другие рецензенты не сделали:)

Обновить метод EntityClassOpening в файле Entity.tt на следующее:

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", string.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)) ? "BaseModel" : _typeMapper.GetTypeName(entity.BaseType)));
}

Найти строку:

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>

и обновите его как:

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel

Спасибо Маноло!

Обновить метод Property в файле Entity.tt на следующий:

public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "private {1} {3};\r\n"+
        "\t{0} {1} {2} \r\n" +
        "\t{{ \r\n" +
            "\t\t{4}get {{ return {3}; }} \r\n" +
            "\t\t{5}set {{ SetProperty(ref {3}, value); }} \r\n" + 
        "\t}}\r\n",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        "_" + Char.ToLowerInvariant(_code.Escape(edmProperty)[0]) + _code.Escape(edmProperty).Substring(1),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

Все готово! Теперь ваши модели будут выглядеть так:

public partial class User : BaseModel
{
    private int _id;
    public int Id 
    { 
        get { return _id; } 
        set { SetProperty(ref _id,value);}
    }

    private string _name;
    public string Name 
    { 
        get { return _name; } 
        set { SetProperty(ref _name , value); }
    }

Пожалуйста, не стесняйтесь (попробуйте) отредактировать это решение, если увидите другие улучшения.

Ответ 3

Я пытался отредактировать решение Брайана Хинчи, но EDIT был отклонен. Затем я отправляю сюда свои добавочные файлы.

Это решение генерирует меньше кода для каждого свойства Setter, используя Атрибут CallerMemberName.

ПРИМЕЧАНИЕ: Я использую EF 6.1, и он работает очень хорошо.

Теперь BaseClass выглядит так.

public abstract class BaseModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }


    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Обновить метод EntityClassOpening в Entity.tt Остается точно так же, как Брайан:

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", string.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)) ? "BaseModel" : _typeMapper.GetTypeName(entity.BaseType)));
}

Как говорит Брайан Запомнить изменение:

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
FOR 
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel

И последнее изменение для метода Свойства

public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "private {1} {3};\r\n"+
        "\t{0} {1} {2} \r\n" +
        "\t{{ \r\n" +
            "\t\t{4}get {{ return {3}; }} \r\n" +
            "\t\t{5}set {{ SetProperty(ref {3}, value); }} \r\n" + 
        "\t}}\r\n",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        "_" + Char.ToLowerInvariant(_code.Escape(edmProperty)[0]) + _code.Escape(edmProperty).Substring(1),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

И наконец, вот класс выглядит следующим образом:

public partial class User : BaseModel
{
    private int _id;
    public int Id 
    { 
        get { return _id; } 
        set { SetProperty(ref _id , value);} 
    }

    private string _name;
    public string Name 
    { 
        get { return _name; } 
        set { SetProperty(ref _name , value);}
    } 
}

Это делает сгенерированные кланы более светлыми.

Недавно я работал с библиотекой PropertyChanged.Fody, но по неизвестным причинам (по крайней мере для меня). Это не работает должным образом несколько раз. Вот почему я здесь. Это решение (решение Брайана) работает каждый раз.

Ответ 4

Решение от Anders выше работает, однако есть несколько ошибок, которые я нашел во время процесса:

На шаге 1, где говорится: "Добавьте следующее в класс CodeStringGenerator", можно добавить только "открытую строку Private (..." ), потому что другие две уже существуют. Таким образом, вам нужно найти их и заменить эти две функции не добавляют их, в противном случае вы получите ошибки. Чтобы найти именно то, куда вам нужно их поместить, выполните поиск "public class CodeStringGenerator" и найдите функции под ним.

В шаге 2 "Добавьте к генератору следующее", вам нужно только добавить строку "using System.ComponentModel" и строки из (и в том числе) "public event PropertyChangedEventHandler...". Опять же, другие строки уже существуют, вы найдете их в верхней части файла .tt.

На шаге 3 "И немного дальше вниз" обе эти петли "foreach" также уже существуют, поэтому их необходимо заменить и не добавить. В конечном итоге в каждый цикл foreach добавляется только одна строка: < # = codeStringGenerator.Private(edmProperty) # > "и" < # = codeStringGenerator.Private(complexProperty) # > "соответственно.

Кроме того, не заменяйте неправильный цикл, есть два дополнительных цикла над теми, которые вам нужно заменить, которые оба цикла проходят через одни и те же объекты... убедитесь, что вы заменили правильные: -)

Я думал, что хочу упомянуть об этом, потому что в качестве новичка MVVM/EF (используемого для использования NHibernate) я должен был выполнить эти настройки для его работы.

Ответ 5

Ну, это зависит от того, что вы пытаетесь сделать. Если вы просто хотите реализовать пользовательские свойства/методы, вы можете использовать функциональность частичных классов. Если вы хотите изменить, скажем, setter/getter ваших свойств в вашем дизайнере объектов, вам придется адаптировать файл шаблона генератора dbContext. Это шаблон T4.

Ответ 6

Я создал следующее для использования с EF 6.1.2, но тестирование было довольно ограниченным, поэтому используйте его на свой страх и риск.

<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF6.Utility.CS.ttinclude"#><#@ 
 output extension=".cs"#><#

const string inputFile = @"Model.edmx";
var textTransform = DynamicTextTransformation.Create(this);
var code = new CodeGenerationTools(this);
var ef = new MetadataTools(this);
var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
var fileManager = EntityFrameworkTemplateFileManager.Create(this);
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))
{
    return string.Empty;
}

WriteHeader(codeStringGenerator, fileManager);

foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
    fileManager.StartNewFile(entity.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
    var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
    var complexProperties = typeMapper.GetComplexProperties(entity);

    if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
    {
#>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public <#=code.Escape(entity)#>()
    {
<#
        foreach (var edmProperty in propertiesWithDefaultValues)
        {
#>
        this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
        }

        foreach (var navigationProperty in collectionNavigationProperties)
        {
#>
        this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
        }

        foreach (var complexProperty in complexProperties)
        {
#>
        this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
        }
#>
    }

<#
    }

    var simpleProperties = typeMapper.GetSimpleProperties(entity);
    if (simpleProperties.Any())
    {
        foreach (var edmProperty in simpleProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var complexProperty in complexProperties)
        {
#>
    <#=codeStringGenerator.Property(complexProperty)#>
<#
        }
    }

    var navigationProperties = typeMapper.GetNavigationProperties(entity);
    if (navigationProperties.Any())
    {
#>

<#
        foreach (var navigationProperty in navigationProperties)
        {
            if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            {
#>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }
#>

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        WhenPropertyChanged(e);

        if (PropertyChanged != null)
        {
            PropertyChanged(this, e);
        }
    }

    partial void WhenPropertyChanged(PropertyChangedEventArgs e);
    #endregion
}
<#
    EndNamespace(code);
}

foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection))
{
    fileManager.StartNewFile(complex.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
{
<#
    var complexProperties = typeMapper.GetComplexProperties(complex);
    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex);

    if (propertiesWithDefaultValues.Any() || complexProperties.Any())
    {
#>
    public <#=code.Escape(complex)#>()
    {
<#
        foreach (var edmProperty in propertiesWithDefaultValues)
        {
#>
        this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
        }

        foreach (var complexProperty in complexProperties)
        {
#>
        this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
        }
#>
    }

<#
    }

    var simpleProperties = typeMapper.GetSimpleProperties(complex);
    if (simpleProperties.Any())
    {
        foreach(var edmProperty in simpleProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var edmProperty in complexProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }
#>
}
<#
    EndNamespace(code);
}

foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection))
{
    fileManager.StartNewFile(enumType.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#
    if (typeMapper.EnumIsFlags(enumType))
    {
#>
[Flags]
<#
    }
#>
<#=codeStringGenerator.EnumOpening(enumType)#>
{
<#
    var foundOne = false;

    foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType))
    {
        foundOne = true;
#>
    <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>,
<#
    }

    if (foundOne)
    {
        this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1);
    }
#>
}
<#
    EndNamespace(code);
}

fileManager.Process();

#>
<#+

public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager)
{
    fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------
<#=codeStringGenerator.UsingDirectives(inHeader: true)#>
<#+
    fileManager.EndBlock();
}

public void BeginNamespace(CodeGenerationTools code)
{
    var codeNamespace = code.VsNamespaceSuggestion();
    if (!String.IsNullOrEmpty(codeNamespace))
    {
#>
namespace <#=code.EscapeNamespace(codeNamespace)#>
{
<#+
        PushIndent("    ");
    }
}

public void EndNamespace(CodeGenerationTools code)
{
    if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion()))
    {
        PopIndent();
#>
}
<#+
    }
}

public const string TemplateId = "CSharp_DbContext_Types_EF6";

public class CodeStringGenerator
{
    private readonly CodeGenerationTools _code;
    private readonly TypeMapper _typeMapper;
    private readonly MetadataTools _ef;

    public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)
    {
        ArgumentNotNull(code, "code");
        ArgumentNotNull(typeMapper, "typeMapper");
        ArgumentNotNull(ef, "ef");

        _code = code;
        _typeMapper = typeMapper;
        _ef = ef;
    }

    public string Property(EdmProperty edmProperty)
    {
        StringBuilder propertyCode = new StringBuilder();
        propertyCode.AppendFormat("private {0} _{1};",_typeMapper.GetTypeName(edmProperty.TypeUsage), _code.Escape(edmProperty));
        propertyCode.AppendFormat(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get{{ return _{2};}} {4}set{{if(_{2} != value){{_{2} = value; OnPropertyChanged(\"{2}\");}}}}}}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));

        return propertyCode.ToString();
    }

    public string NavigationProperty(NavigationProperty navProp)
    {
        var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get; {4}set; }}",
            AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
            navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
            _code.Escape(navProp),
            _code.SpaceAfter(Accessibility.ForGetter(navProp)),
            _code.SpaceAfter(Accessibility.ForSetter(navProp)));
    }

    public string AccessibilityAndVirtual(string accessibility)
    {
        return accessibility + (accessibility != "private" ? " virtual" : "");
    }

    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1}partial class {2} : INotifyPropertyChanged{3}",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(", ", _typeMapper.GetTypeName(entity.BaseType)));
    }

    public string EnumOpening(SimpleType enumType)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} enum {1} : {2}",
            Accessibility.ForType(enumType),
            _code.Escape(enumType),
            _code.Escape(_typeMapper.UnderlyingClrType(enumType)));
        }

    public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)
    {
        var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
        foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
        {
            var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
            var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
            var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";
            writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);
        }
    }

    public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);

        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} IQueryable<{1}> {2}({3})",
            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
            _code.Escape(edmFunction),
            string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));
    }

    public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);

        return string.Format(
            CultureInfo.InvariantCulture,
            "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",
            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
            edmFunction.NamespaceName,
            edmFunction.Name,
            string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),
            _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));
    }

    public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);
        var returnType = _typeMapper.GetReturnType(edmFunction);

        var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());
        if (includeMergeOption)
        {
            paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
        }

        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2}({3})",
            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
            returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
            _code.Escape(edmFunction),
            paramList);
    }

    public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);
        var returnType = _typeMapper.GetReturnType(edmFunction);

        var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));
        if (includeMergeOption)
        {
            callParams = ", mergeOption" + callParams;
        }

        return string.Format(
            CultureInfo.InvariantCulture,
            "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",
            returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
            edmFunction.Name,
            callParams);
    }

    public string DbSet(EntitySet entitySet)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} virtual DbSet<{1}> {2} {{ get; set; }}",
            Accessibility.ForReadOnlyProperty(entitySet),
            _typeMapper.GetTypeName(entitySet.ElementType),
            _code.Escape(entitySet));
    }

    public string UsingDirectives(bool inHeader, bool includeCollections = true)
    {
        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
            ? string.Format(
                CultureInfo.InvariantCulture,
                "{0}using System;" + Environment.NewLine + 
                "using System.ComponentModel;{1}" +
                "{2}",
                inHeader ? Environment.NewLine : "",
                includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
                inHeader ? "" : Environment.NewLine)
            : "";
    }
}

public class TypeMapper
{
    private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";

    private readonly System.Collections.IList _errors;
    private readonly CodeGenerationTools _code;
    private readonly MetadataTools _ef;

    public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)
    {
        ArgumentNotNull(code, "code");
        ArgumentNotNull(ef, "ef");
        ArgumentNotNull(errors, "errors");

        _code = code;
        _ef = ef;
        _errors = errors;
    }

    public static string FixNamespaces(string typeName)
    {
        return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");
    }

    public string GetTypeName(TypeUsage typeUsage)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
    }

    public string GetTypeName(EdmType edmType)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: null);
    }

    public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
    }

    public string GetTypeName(EdmType edmType, string modelNamespace)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
    }

    public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)
    {
        if (edmType == null)
        {
            return null;
        }

        var collectionType = edmType as CollectionType;
        if (collectionType != null)
        {
            return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));
        }

        var typeName = _code.Escape(edmType.MetadataProperties
                                .Where(p => p.Name == ExternalTypeNameAttributeName)
                                .Select(p => (string)p.Value)
                                .FirstOrDefault())
            ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
                _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :
                _code.Escape(edmType));

        if (edmType is StructuralType)
        {
            return typeName;
        }

        if (edmType is SimpleType)
        {
            var clrType = UnderlyingClrType(edmType);
            if (!IsEnumType(edmType))
            {
                typeName = _code.Escape(clrType);
            }

            typeName = FixNamespaces(typeName);

            return clrType.IsValueType && isNullable == true ?
                String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :
                typeName;
        }

        throw new ArgumentException("edmType");
    }

    public Type UnderlyingClrType(EdmType edmType)
    {
        ArgumentNotNull(edmType, "edmType");

        var primitiveType = edmType as PrimitiveType;
        if (primitiveType != null)
        {
            return primitiveType.ClrEquivalentType;
        }

        if (IsEnumType(edmType))
        {
            return GetEnumUnderlyingType(edmType).ClrEquivalentType;
        }

        return typeof(object);
    }

    public object GetEnumMemberValue(MetadataItem enumMember)
    {
        ArgumentNotNull(enumMember, "enumMember");

        var valueProperty = enumMember.GetType().GetProperty("Value");
        return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);
    }

    public string GetEnumMemberName(MetadataItem enumMember)
    {
        ArgumentNotNull(enumMember, "enumMember");

        var nameProperty = enumMember.GetType().GetProperty("Name");
        return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);
    }

    public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        var membersProperty = enumType.GetType().GetProperty("Members");
        return membersProperty != null 
            ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)
            : Enumerable.Empty<MetadataItem>();
    }

    public bool EnumIsFlags(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");
        return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);
    }

    public bool IsEnumType(GlobalItem edmType)
    {
        ArgumentNotNull(edmType, "edmType");

        return edmType.GetType().Name == "EnumType";
    }

    public PrimitiveType GetEnumUnderlyingType(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);
    }

    public string CreateLiteral(object value)
    {
        if (value == null || value.GetType() != typeof(TimeSpan))
        {
            return _code.CreateLiteral(value);
        }

        return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);
    }

    public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
    {
        ArgumentNotNull(types, "types");
        ArgumentNotNull(sourceFile, "sourceFile");

        var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
        if (types.Any(item => !hash.Add(item)))
        {
            _errors.Add(
                new CompilerError(sourceFile, -1, -1, "6023",
                    String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));
            return false;
        }
        return true;
    }

    public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)
    {
        return GetItemsToGenerate<SimpleType>(itemCollection)
            .Where(e => IsEnumType(e));
    }

    public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType
    {
        return itemCollection
            .OfType<T>()
            .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))
            .OrderBy(i => i.Name);
    }

    public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)
    {
        return itemCollection
            .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))
            .Select(g => GetGlobalItemName(g));
    }

    public string GetGlobalItemName(GlobalItem item)
    {
        if (item is EdmType)
        {
            return ((EdmType)item).Name;
        }
        else
        {
            return ((EntityContainer)item).Name;
        }
    }

    public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
    }

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
    }

    public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)
    {
        return type.NavigationProperties.Where(np => np.DeclaringType == type);
    }

    public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)
    {
        return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
    }

    public FunctionParameter GetReturnParameter(EdmFunction edmFunction)
    {
        ArgumentNotNull(edmFunction, "edmFunction");

        var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");
        return returnParamsProperty == null
            ? edmFunction.ReturnParameter
            : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();
    }

    public bool IsComposable(EdmFunction edmFunction)
    {
        ArgumentNotNull(edmFunction, "edmFunction");

        var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");
        return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);
    }

    public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)
    {
        return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
    }

    public TypeUsage GetReturnType(EdmFunction edmFunction)
    {
        var returnParam = GetReturnParameter(edmFunction);
        return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);
    }

    public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)
    {
        var returnType = GetReturnType(edmFunction);
        return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
    }
}

public static void ArgumentNotNull<T>(T arg, string name) where T : class
{
    if (arg == null)
    {
        throw new ArgumentNullException(name);
    }
}
#>

Ответ 7

Я работаю с Visual Basic и, как правило, пользуюсь рефакторингом устаревших и неслоистых архитектур.

Итак, я разработал, как это сделать, используя ответы сверху, но в Entity Framework 6.1.3 и .net 4.6.1.

Я перевел из ответов, данных другими, поэтому я могу только взять кредит за бит повторного открытия и перевода, которые я сделал сегодня вечером.

Проект для этого довольно крошечный, он winforms, и я хотел привязать данные, а не множество ручных обновлений для элементов управления. Я НЕ хотел добавить еще большую сложность, добавив больше разделения, поскольку здесь не было достаточной выгоды. Там достаточно, чтобы оправдать это:).

Я надеюсь, что это поможет другим кодам VB.

Базовый класс:

Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public MustInherit Class BaseModel
    Implements INotifyPropertyChanged
    Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional propertyName As String = Nothing) As Boolean
        If Equals(storage, value) Then Return False
        storage = value
        OnPropertyChanged(propertyName)
        Return True
    End Function

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

Открытое свойство string не существует в файле VB TT (убедитесь, что ModelName.tt не является ModelName.Context.tt). Обратите внимание на использование условного выражения для сравнения с нулевым значением. Если у вас этого нет, вы не получите увольнения!

Public Function AnyProperty(accessibility As String, type As String, name As String, getterAccessibility As String, setterAccessibility As String, defaultValue As String)
            Dim CompareLine = if(type.contains("Null"), $"if Not Nullable.Equals({name}, value) ", $"if {name} <> value ")
            Return String.Format( _
                CultureInfo.InvariantCulture, _
                "{6}    Private _{0} As {1}{2}{6}" & _
                "    {3} Property {0} As {1}{6}" & _
                "        {4}Get{6}" & _
                "            Return _{0}{6}" & _
                "        End Get{6}" & _
                "        {5}Set(ByVal value As {1}){6}" & _
                "             {7} then  SetProperty(_{0}, value){6}" & _
                "        End Set{6}" & _
                "    End Property",  _
                name, _
                type, _
                defaultValue, _
                accessibility, _
                getterAccessibility, _
                setterAccessibility, _
                Environment.NewLine,
                CompareLine)
 End Function

EntityClassOpening

Public Function EntityClassOpening(entity As EntityType) As String
        Return String.Format( _
            CultureInfo.InvariantCulture, _
            "Partial {0} {1}Class {2}{3}", _
            Accessibility.ForType(entity), _
            _code.SpaceAfter(_code.MustInheritOption(entity)), _
            _code.Escape(entity), _
            _code.StringBefore(Environment.Newline & " Inherits ", If(String.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)), "BaseModel", _typeMapper.GetTypeName(entity.BaseType))))
  End Function

Если я найду что-нибудь существенное, чтобы добавить к этому, я сделаю это.

Пожалуйста, обратите внимание на мой прецедент: легкий проект VB, без обоснования для введения уровней MVVM/MVC и репозитория, но есть множество причин, чтобы требовать привязки данных, а не раздражать условные обновления и от элементов управления - многие из которых имеют EditValue, которые имеют тип объекта (элементы управления Devexpress winforms).

Ответ 8

Вам следует продолжить использование ObjectContext, если вы хотите получать уведомления об изменении свойств при непосредственном привязке к классам модели данных.

Легкие классы DBContext предназначены для таких шаблонов, как MVVM или MVVMC, где ваша модель представления реализует уведомления об изменении свойств, а ваш пользовательский интерфейс связывается только с свойствами модели представления. Вы никогда не привязываетесь к классам моделей данных в этих шаблонах.