2
\$\begingroup\$

The question might seem a little bit long, but I will be very glad if you have read it till the end and told me your opinions.

Let's say I have such entity:

public class FirstEntity
{
    public int Id { get; set; }
    ...

    public virtual SecondEntity SecondEntity { get; set; }
    public int SecondEntityId { get; set; }

    public virtual ICollection<ThirdEntity> ThirdEntities { get; set; }
}

And here is the SecondEntity and ThirdEntity:

public class SecondEntity
{
    public int Id { get; set; }
    ...

    public virtual FirstEntity FirstEntity { get; set; }
}

public class ThirdEntity
{
    public int Id { get; set; }
    ...

    public virtual FirstEntity FirstEntity { get; set; }
}

While inserting entity I am setting it's state to Added:

 Context.Entry(entity).State = EntityState.Added;

In this case, all child entities states are changing to Added also.

The problem is I must not insert some entities if they already exist in the database. So, for example if some SecondEntity exists in the database, then I must change it's state to Detached, but also I must set SecondEntityId value to existing SecondEntity in the database.

IMHO, it is pretty common problem. But, I can't find any question like this in SE.

I have solved this problem. But I wonder, is my algorithm any good?

I have created two attributes: [MustBeChecked] and [ModifyState("SecondEntityId")]

Logic is that, MustBeChecked is the navigational property which includes navigational properties which must be checked. And these properties has decorated with ModifyState attribute and the name of the property which must be set to the key value of the existing row in the database.

For example, FirstEntity will look like that:

[MustBeChecked]
public class FirstEntity
{
    public int Id { get; set; }
    ...

    [ModifyState("SecondEntityId")]
    public virtual SecondEntity SecondEntity { get; set; }
    public int SecondEntityId { get; set; }

    [ModifyState]
    public virtual ICollection<ThirdEntity> ThirdEntities { get; set; }
}

Also I have one type per entity which has DefineState method. I call it with relfection in runtime. It checks if this entity exists in the database or not and then sets the property value in parent object. For example, one of them:

public class SecondEntityState : IStateDefiner<SecondEntity>
{
    public Phone DefineState(DbContext context, SecondEntity model, object parent, string propertyName)
    {
        var entity = context.Set<SecondEntity>().AsQueryable().FilterByUniqueProperties(model).SingleOrDefault();

        if (entity != null)
        {
            if (!String.IsNullOrEmpty(propertyName))
            {
                parent.GetType().GetProperty(propertyName).SetValue(parent, entity.Id);
            }

            context.Entry(model).State = System.Data.EntityState.Detached;
        }

        return model;
    }
}

And I am iterating over entity properties recursively to find such navigational properties and process over them.

public void ModifyState<TEntity>(TEntity entity)
        {
            var properties = entity.GetType()
                .GetPropertiesWithAttribute<MustBeCheckedAttribute>();

            // Iterate over attribute details
            foreach (var prop in properties)
            {
                // Check if the property is collection
                if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType))
                {
                    var collection = entity.GetType()
                        .GetProperty(prop.Name)
                        .GetValue(entity);

                    foreach (var collectionElement in collection as List<object>)
                    {
                        ModifyState(collectionElement);
                        ProcessOverProperties(collectionElement);
                    }
                }
                else
                {
                    ModifyState(entity);
                    ProcessOverProperties(entity);
                }
            }
        }

        /// <summary>
        /// Will find properties which has decorated with ModifyState attribute.
        /// And then will select property and IdContainerPropertyName value for this attribute.
        /// We are going to use these informations for detaching navigational property
        /// and setting corresponding property's value
        /// </summary>
        /// <typeparam name="TEntity">Entity type</typeparam>
        /// <param name="entity">Entity object</param>
        private void ProcessOverProperties<TEntity>(TEntity entity)
        {
            var modifyStateAttributes = entity.GetType()
                .GetPropertiesWithAttribute<ModifyStateAttribute>()
                .Select(prop =>
                    new
                    {
                        Property = prop,
                        IdContainer = (Attribute.GetCustomAttribute(prop, typeof(ModifyStateAttribute)) as ModifyStateAttribute).IdContainerPropertyName
                    });

            // Iterate over attribute details
            foreach (var attributeInformation in modifyStateAttributes)
            {
                var stateDefinerInstance = EntityStateFactory.CreateStateDefiner(attributeInformation.Property.PropertyType);

                MethodInfo methodInfo = stateDefinerInstance.GetType().GetMethod("DefineState");
                methodInfo.Invoke(stateDefinerInstance, new object[] 
                    { 
                        this, 
                        entity.GetType().GetProperty(attributeInformation.Property.Name).GetValue(entity), 
                        entity, 
                        attributeInformation.IdContainer
                    });
            }
        }

Is my logic any good?

\$\endgroup\$
  • 1
    \$\begingroup\$ This is what GraphDiff is for \$\endgroup\$ – Gert Arnold May 22 '15 at 7:06
  • \$\begingroup\$ @GertArnold Really? :(( It is a really pity for me. I have write so many code )) I will check it right now \$\endgroup\$ – Farhad Jabiyev May 22 '15 at 7:15
  • \$\begingroup\$ @GertArnold Also, in my case, I need to get ID of already inserted data from the database and set it to property in new entity. And for finding ig this type of entity exist in the database, I need to execute my own extension method. Is this type of logic supported in GraphDiff. \$\endgroup\$ – Farhad Jabiyev May 22 '15 at 8:21
  • \$\begingroup\$ I don't know, I never used it, but I see lots of references to it at StackOverflow. But what you describe should not be necessary if you set navigation properties in stead of primitive FK values, and let EF worry about setting foreign keys in time. \$\endgroup\$ – Gert Arnold May 22 '15 at 8:44
2
\$\begingroup\$

I'm going to answer in the context of comparing this to GraphDiff since it was mentioned in the comments.

First, make sure you have a need to update objects in this fashion. I'm assuming you do.

Implementation-wise, the main difference between GraphDiff and this approach is that GraphDiff builds a graph object and then visits it, while this approach uses recursion for a more procedural approach.

As for the use of reflection, I think it's required to solve this problem. I can't see a way around it.

I see this as a valid approach. It requires custom attributes on entities and some boilerplate code in places, but short of writing a library that extends EF objects, I think you kind of have to do something like this.

As long as you can verify that you won't get into

  1. circular reference issues
  2. stack overflows from recursing too deeply

then I would say it's a valid approach. It's clean, and it appears easy enough to maintain.

Two suggestions:

  1. I would suggest storing your objects you get from reflecting on types in a static dictionary so you don't have to use reflection every time, just the first time.

  2. Consider making an abstract or at least a parent class for a base implementation of your State classes. I think there's some code in your DefineState methods that you that you can avoid duplicating in every method definition by pulling out into a base implementation. Can you possibly use generic parameters and/or one generic method?

Overall though, let performance be the judge. As long as your app isn't taking a significant hit from this implementation, I say you should congratulate yourself on finding a solution to a rather complex problem.

\$\endgroup\$
  • \$\begingroup\$ Thanks very much. To tell the truth I have updated it with a buch of configurations fluently. And i am storing this configurations in static classes. Also I added update, delete and some other features. And I tried to use reflection as few asI can. Instead of that I tried to use generics where I can.. In 2-3 month I want to publish it in nuget. \$\endgroup\$ – Farhad Jabiyev Aug 27 '15 at 2:52
  • \$\begingroup\$ @FarhadJabiyev Excellent! \$\endgroup\$ – moarboilerplate Aug 27 '15 at 2:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.