HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Entity Framework Code First Data Updater

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
updaterfirstcodedataentityframework

Problem

For a given Entity Framework data set, I have written several adapters to import data into my entity models. Rather than manually write mapping code for each entity model, I tried for a generalized approach using reflection.

My requirements are:

  • The updater must create the entity if it does not exist, or update the existing entity with any changed properties or relationships.



  • External data are often subsets; therefore, do not delete entities if they are not in external data



  • If external data reference other entities which do not exist, eg tags, the updater must create them and ensure the references are still valid, while respecting key constraints.



My entities implement an interface, IEntity:

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


EntityEquals compares entities based on their natural keys. An implementation might look like:

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

    public T CreateEmpty() 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;
    }
}


I invoke this like

EntityDataUpdater.Update(context, externalEntitiesList);


And here's the implementation. Comments and feedback greatly appreciated!

```
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 entities)
{
if (!entities.Any()) return;

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

foreach (var en

Solution

I personally don't like exposing Ids as their base data type (in your case, int) for a couple of reasons:

-
Somewhere, in client code, someone can assign an int of one type of
entity to another by mistake and nothing will stop you (i.e. no
strong typing). This can wreak havoc in the DB.

-
If the underlying data type ever needs to change (e.g. you change
databases or figure your int now needs to be a long/bigint), you
have to find all places where you reference it as an int and
change it. Encapsulating the Id keeps it localized to the DAL.

So I tend to wrap that up thus (the Origin property I normally leave empty, but it can represent different schemas in a database where you may have entity name overlap, etc.):

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

    /// 
    /// The model instance identifier for the model object that this  refers to.
    /// Typically, this is a database key, file name, or some other unique identifier.
    /// The expected data type of the identifier.
    /// 
    /// The expected data type of the identifier.
    /// The unique key as the data type specified.
    TKeyDataType GetKey();

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


I have a base class handle the Origin property for me:

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

        internal set;
    }

    /// 
    /// The model instance identifier for the model object that this  refers to.
    /// Typically, this is a database key, file name, or some other unique identifier.
    /// 
    /// The expected data type of the identifier.
    /// The unique key as the data type specified.
    public abstract TKeyDataType GetKey();

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


Finally, I have a couple implementations I use currently - one for int and another for Guid. Here's the int implementation:

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

set;
}

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

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

///
/// Performs an implicit conversion from to .
///
/// The identifier.
/// The result of the conversion.
public static implicit operator int(IntId id)
{
return id == null ? int.MinValue : id.GetKey();
}

///
/// Performs an implicit conversion from to .
///
/// The identifier.
/// The result of the conversion.
public static implicit operator IntId(int id)
{
return new IntId { Id = id };
}

///
/// Determines whether the specified is equal to the current
/// .
///
/// The to compare

Code Snippets

/// <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);
}
/// <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 fun
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);
}
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;
    }
}

Context

StackExchange Code Review Q#6107, answer score: 2

Revisions (0)

No revisions yet.