Существо Рамочного Код Первого Обновления Данных


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

Мои требования:

  • Апдейтер должен создать объект, если он не существует или обновить существующий объект при любом изменении свойств или отношений.
  • Внешние данные часто подмножеств; поэтому, не удалять объекты, если они не во внешних данных
  • Если внешняя ссылка данные других субъектов, которые отсутствуют, например, теги, обновления должны их создать и обеспечить ссылки еще действительны при соблюдении основных ограничений.

Мои субъекты реализуют интерфейс, метод ientity:

public interface IEntity
{
    int Id { get; set; }
    T CreateEmpty<T>() where T:class,IEntity;
    bool EntityEquals(IEntity entity);
}

EntityEquals сравнивает лиц, основанные на их природных ключей. Реализация могла бы выглядеть так:

public class Tag : IEntity
{
    public int Id { get; set; }
    // Name is a natural key
    public string Name { get; set; }

    public T CreateEmpty<T>() where T : class, IEntity
    {
        return new Tag() as T;
    }

    public bool EntityEquals(IEntity entity)
    {
        var e = entity as Tag;
        return e != null && Name==e.Name;
    }
}

Я призываю это нравится

EntityDataUpdater.Update(context, externalEntitiesList);

И вот реализация. Комментарии и отзывы с благодарностью!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using Project.Models;

namespace Project.Models.Adapters
{
    public static class EntityDataUpdater
    {

        public static void Update(DbContext context, IEnumerable<IEntity> entities)
        {
            if (!entities.Any()) return;

            var set = context.Set(entities.First().GetType());
            set.Load(); // prefetch data to memory

            foreach (var entity in entities)
            {
                var tEntity = entity.ImportOrUpdate(context);

                // if newly imported, add it
                if (tEntity.Id == 0) set.Add(tEntity);
                // otherwise, indicate that it is updated
                else context.Entry(tEntity).State = EntityState.Modified;
            }

            context.SaveChanges();
        }
    }

    static class EntityCopyExtensions
    {
        private static bool TryFindIn<T>(this T entity, DbContext context, out T o) where T : class, IEntity
        {
            var set = context.Set(entity.GetType());

            o = set.Local.Cast<IEntity>()
                .FirstOrDefault(item => item.EntityEquals(entity)) as T // check local first
                ?? ((IEnumerable)set).Cast<IEntity>()
                .FirstOrDefault(item => item.EntityEquals(entity)) as T; // then check DB
            return o != null;
        }

        private static IEntity FindOrCreateIn(this IEntity entityIn, DbContext context)
        {
            // if item exists, return it; otherwise create it
            IEntity entityOut;
            return !entityIn.TryFindIn(context, out entityOut) ? entityIn.ImportOrUpdate(context) : entityOut;
        }

        public static T ImportOrUpdate<T>(this T entityIn, DbContext context) where T : class, IEntity
        {
            var entityT = entityIn.GetType();

            T entityOut;
            if(!entityIn.TryFindIn(context, out entityOut))
            {
                entityOut = entityIn.CreateEmpty<T>();
            }

            // copy properties
            foreach (var property in entityT.GetProperties()
                        .Where(p => p.Name != "Id" &&
                            p.CanWrite && (
                            p.PropertyType == typeof(string) ||
                            p.PropertyType == typeof(int) ||
                            p.PropertyType == typeof(DateTime)
                        ))){
                var value = property.GetValue(entityIn, null);
                if (value != null) property.SetValue(entityOut, value, null);
            }

            // copy direct references
            foreach (var reference in entityT.GetProperties()
                        .Where(r => r.PropertyType.IsImplementationOf(typeof(IEntity)))
                    ){
                var origValue = (IEntity) reference.GetValue(entityIn, null);
                reference.SetValue(entityOut,origValue.FindOrCreateIn(context),null);
            }

            // copy collections of references
            foreach (var reference in entityT.GetProperties()
                .Where(r=>
                    r.PropertyType.IsImplementationOf(typeof(IEnumerable)) && 
                    r.PropertyType.IsGenericType &&
                    r.PropertyType.GetGenericArguments().First().IsImplementationOf(typeof(IEntity))
                )){

                var itemsIn = (IEnumerable) (reference.GetValue(entityIn, null));

                // itemsOut begins with all items from entityOut
                var itemsOut = ((IEnumerable) (reference.GetValue(entityOut, null))).Cast<IEntity>().ToList();

                // then adds any new items from entityIn
                foreach (IEntity item in itemsIn)
                {
                    var itemOut = item.FindOrCreateIn(context);
                    //only add new items
                    if (!itemsOut.Any(i => i.Id == itemOut.Id) || item.Id==0)
                    {
                        itemsOut.Add(item.FindOrCreateIn(context));
                    }
                }

                reference.SetValue(entityOut,itemsOut.CastTo(reference.PropertyType),null);
            }

            return entityOut;
        }

        /// <summary>
        /// Cast each element in <paramref name="list"/> to type of the first object,
        /// or null
        /// </summary>
        private static IEnumerable CastTo(this IList list, Type targetT)
        {
            if (!(targetT.IsImplementationOf(typeof(IEnumerable)) && targetT.IsGenericType)) 
                throw new ArgumentException("targetT must be an IEnumerable of T.");

            var itemT = targetT.GetGenericArguments().First();

            var ret = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(new[] {itemT}));
            foreach (var i in list)
                ret.Add(i);

            return ret;
        }

        /// <summary>
        /// Returns true if type <paramref name="t"/> implements interface <paramref name="i"/>
        /// </summary>
        private static bool IsImplementationOf(this Type t, Type i)
        {
            if (!i.IsInterface) throw new ApplicationException("Method must be of type Interface");
            return t.GetInterfaces().Contains(i);
        }
        /// <summary>
        /// Returns true if type <paramref name="t"/> implements interface <paramref name="i"/>
        /// </summary>
        private static bool IsImplementationOf(this Type t, string i)
        {
            if (!Type.GetType(i, true).IsInterface) throw new ApplicationException("Method must be of type Interface");
            return t.GetInterface(i) != null;
        }    
    }
}


3066
5
задан 16 ноября 2011 в 11:11 Источник Поделиться
Комментарии
1 ответ

Я лично не люблю выставляя ИДкак их базовые типы данных (в вашем случае, инт) по нескольким причинам:


  1. Где-то в клиентском коде, кто-то может присвоить инт одного типа
    лица к другому по ошибке и ничто не остановит вас (т. е. нет
    строгая типизация). Это может посеять хаос в БД.

  2. Если базовый тип данных не должен меняться (например, вы изменяете
    баз данных или рисунок вашего инт сейчас должен быть длинный/тип bigint), вы
    должны найти все места, где можно ссылаться на него как Инти
    изменить его. Инкапсуляция идентификатор держит она локализуется в даль.

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

/// <summary>
/// Defines an interface for an object's unique key in order to abstract out the underlying key
/// generation/maintenance mechanism.
/// </summary>
/// <typeparam name="T">The type the key is representing.</typeparam>
public interface IModelId<T> where T : class, IEntity<T>
{
/// <summary>
/// Gets a string representation of the domain the model originated from.
/// </summary>
/// <value>The origin.</value>
string Origin
{
get;
}

/// <summary>
/// The model instance identifier for the model object that this <see cref="IModelId{T}"/> refers to.
/// Typically, this is a database key, file name, or some other unique identifier.
/// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
/// </summary>
/// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
/// <returns>The unique key as the data type specified.</returns>
TKeyDataType GetKey<TKeyDataType>();

/// <summary>
/// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal; otherwise
/// <c>false</c> is returned. All implementations must also override the equal operator.
/// </summary>
/// <param name="obj">The identifier to compare against.</param>
/// <returns><c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.</returns>
bool Equals(IModelId<T> obj);
}

У меня есть базовый класс обработки происхождение собственность на меня:

/// <summary>
/// Represents an object's unique key in order to abstract out the underlying key generation/maintenance mechanism.
/// </summary>
/// <typeparam name="T">The type the key is representing.</typeparam>
public abstract class ModelIdBase<T> : IModelId<T> where T : class, IEntity<T>
{
/// <summary>
/// Gets a string representation of the domain the model originated from.
/// </summary>
public string Origin
{
get;

internal set;
}

/// <summary>
/// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
/// Typically, this is a database key, file name, or some other unique identifier.
/// </summary>
/// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
/// <returns>The unique key as the data type specified.</returns>
public abstract TKeyDataType GetKey<TKeyDataType>();

/// <summary>
/// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
/// otherwise <c>false</c> is returned. All implementations must also override the equal operator.
/// </summary>
/// <param name="obj">The identifier to compare against.</param>
/// <returns>
/// <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
/// </returns>
public abstract bool Equals(IModelId<T> obj);
}

Наконец, у меня есть пара реализаций я пользуюсь в настоящее время - один для типа int , а другой-для идентификатора GUID. Вот инт реализации:

/// <summary>
/// Represents an abstraction of the database key for a Model Identifier.
/// </summary>
/// <typeparam name="T">The expected owner data type for this identifier.</typeparam>
[DebuggerDisplay("Origin={Origin}, Integer Identifier={id}")]
public sealed class IntId<T> : ModelIdBase<T> where T : class, IEntity<T>
{
/// <summary>
/// Gets or sets the unique ID.
/// </summary>
/// <value>The unique ID.</value>
internal int Id
{
get;

set;
}

/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="intIdentifier1">The first Model Identifier to compare.</param>
/// <param name="intIdentifier2">The second Model Identifier to compare.</param>
/// <returns>
/// <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
/// </returns>
public static bool operator ==(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
{
return object.Equals(intIdentifier1, intIdentifier2);
}

/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="intIdentifier1">The first Model Identifier to compare.</param>
/// <param name="intIdentifier2">The second Model Identifier to compare.</param>
/// <returns>
/// <c>true</c> if the instances are equal; otherwise <c>false</c> is returned.
/// </returns>
public static bool operator !=(IntId<T> intIdentifier1, IntId<T> intIdentifier2)
{
return !object.Equals(intIdentifier1, intIdentifier2);
}

/// <summary>
/// Performs an implicit conversion from <see cref="IntId{T}"/> to <see cref="System.Int32"/>.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator int(IntId<T> id)
{
return id == null ? int.MinValue : id.GetKey<int>();
}

/// <summary>
/// Performs an implicit conversion from <see cref="System.Int32"/> to <see cref="IntId{T}"/>.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator IntId<T>(int id)
{
return new IntId<T> { Id = id };
}

/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current
/// <see cref="T:System.Object"/>.
/// </summary>
/// <param name="obj">The <see cref="T:System.Object"/> to compare with the current
/// <see cref="T:System.Object"/>.</param>
/// <returns>true if the specified <see cref="T:System.Object"/> is equal to the current
/// <see cref="T:System.Object"/>; otherwise, false.</returns>
/// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
public override bool Equals(object obj)
{
return this.Equals(obj as IModelId<T>);
}

/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="T:System.Object"/>.
/// </returns>
public override int GetHashCode()
{
unchecked
{
var hash = 17;

hash = (23 * hash) + (this.Origin == null ? 0 : this.Origin.GetHashCode());
return (31 * hash) + this.GetKey<int>().GetHashCode();
}
}

/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
return this.Origin + ":" + this.GetKey<int>().ToString(CultureInfo.InvariantCulture);
}

/// <summary>
/// Performs an equality check on the two model identifiers and returns <c>true</c> if they are equal;
/// otherwise <c>false</c> is returned. All implementations must also override the equal operator.
/// </summary>
/// <param name="obj">The identifier to compare against.</param>
/// <returns>
/// <c>true</c> if the identifiers are equal; otherwise <c>false</c> is returned.
/// </returns>
public override bool Equals(IModelId<T> obj)
{
if (obj == null)
{
return false;
}

return (obj.Origin == this.Origin) && (obj.GetKey<int>() == this.GetKey<int>());
}

/// <summary>
/// Generates an object from its string representation.
/// </summary>
/// <param name="value">The value of the model's type.</param>
/// <returns>A new instance of this class as it's interface containing the value from the string.</returns>
internal static ModelIdBase<T> FromString(string value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}

int id;
var originAndId = value.Split(new[] { ":" }, StringSplitOptions.None);

if (originAndId.Length != 2)
{
throw new ArgumentOutOfRangeException("value", "value must be in the format of Origin:Identifier");
}

return int.TryParse(originAndId[1], NumberStyles.None, CultureInfo.InvariantCulture, out id)
? new IntId<T> { Id = id, Origin = originAndId[0] }
: null;
}

/// <summary>
/// The model instance identifier for the model object that this <see cref="ModelIdBase{T}"/> refers to.
/// Typically, this is a database key, file name, or some other unique identifier.
/// </summary>
/// <typeparam name="TKeyDataType">The expected data type of the identifier.</typeparam>
/// <returns>The unique key as the data type specified.</returns>
public override TKeyDataType GetKey<TKeyDataType>()
{
return (TKeyDataType)Convert.ChangeType(this.Id, typeof(TKeyDataType), CultureInfo.InvariantCulture);
}
}

Ваш интерфейс для лица в таком случае определяется как:

public interface IEntity<T> where T : class, IEntity<T>
{
IModelId<T> Id { get; set; }
U CreateEmpty<U>() where U : class,IEntity<T>;
bool EntityEquals(IEntity<T> entity);
}

Дал бы возможность установить ID объекта как такового (с помощью тега класса):

public class Tag : IEntity<Tag>
{
public IModelId<Tag> Id { get; set; }
// Name is a natural key
public string Name { get; set; }

public T CreateEmpty<T>() where T : class, IEntity<Tag>
{
return new Tag() as T;
}

public bool EntityEquals(IEntity<Tag> entity)
{
var e = entity as Tag;
return e != null && Name == e.Name;
}
}

Установка кода (явно не реальные):

Tag tag = new Tag();

tag.Id = new IntId<Tag> { Id = 42 };
Console.WriteLine(tag.Id.GetKey<int>());

Ваш вспомогательных классов будет также нужны некоторые доработки, чтобы справиться с этим. Теперь, все это было сказано и сделано, я не уверен, как основы организации вяжется с таким понятием без помощи.

2
ответ дан 29 декабря 2011 в 05:12 Источник Поделиться