How do I create a custom value injection to map my entity to my view model? Attempt included

末鹿安然 提交于 2019-12-12 09:57:55

问题


I'm trying to use the ValueInjector (latest version from NuGet) to inject my view model with data from my EntityFramework Code First object. I'll want to do the reverse of this as well, but this is the attempt I'm starting with.

I've researched the various mappings and have tried many of them. I haven't found one that does what I need and I think my need is a rather basic one (there are several of these questions on SO and each seems to get a different answer, which is fine.).

I've really tried to do my due diligence on this. I've googled and trolled CodePlex and downloaded the ProDinner application (got it up and running and stepped through the code in debug).

I'm at a point where I have my own ValueInjection class to handle the injecton. I'm about to paste my code and objects in a pretty friendly way so you can grab and run them. Mind you I have a solution that uses the base LoopValueInjection, but I don't like it because it'd require manual plumbing for injections going from entity to mapping and then mapping to entity. The ProDinner example had a more templated approach, which I liked, but I couldn't adapt it to my needs.

I think where my code is messing up logic wise is that I don't understand how to force a recursive injection to occur if the source property type is not a simple object. In this example, the Person.Address.* properties will match by name and type those on the PersonViewModel class; however, the injection loops over the properties on Person and tries to match on the name and type of the Person.Address property.

I thought the line

        Object result = Activator.CreateInstance(c.SourceProp.Type)
            .InjectFrom<CloneInjection>(c.SourceProp.Value);

Would perform this recursion, but I don't think it does.

So...Can anyone give me any direction on how to fix this?

//////  ENTITY MODELS

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

//////  VIEW MODEL 

public class PersonViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int PersonId { get; set; }
    public int AddressId { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; } 
}   

////// CUSTOM INJECTOR
public class EntityToViewModel : ConventionInjection
{

    protected override bool Match(ConventionInfo c)
    {
        //ignore int = 0 and DateTime = to 1/01/0001
        if (c.SourceProp.Type == typeof(DateTime) && (DateTime)c.SourceProp.Value == default(DateTime) ||
            (c.SourceProp.Type == typeof(int) && (int)c.SourceProp.Value == default(int)))
            return false;

        if (c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Value != null)
            return true;

        if (c.SourceProp.Type == typeof(int) &&
            c.TargetProp.Type == typeof(int) )
        {
            if( "id".Equals(c.SourceProp.Name.ToLower()) &&                 
                c.TargetProp.Name.ToLower().EndsWith("id") &&
                c.TargetProp.Name.StartsWith(c.Source.Type.Name))
            return true;
        }

        //Transition logic to SetValue for value types. This should 
        //allow Address values on Person to be set.
        if (!c.SourceProp.Type.IsPrimitive || c.SourceProp.Type.Equals(typeof(string)))
            return true;

        return false;

        //put id logic matching here
        //return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Value != null;
    }

    protected override object SetValue(ConventionInfo c)
    {
        //If type is primative or string return the value as is   
        if (c.SourceProp.Type.IsPrimitive || c.SourceProp.Type.Equals(typeof(string)))
            return c.SourceProp.Value;

        Object result = Activator.CreateInstance(c.SourceProp.Type)
            .InjectFrom<CloneInjection>(c.SourceProp.Value);

        //for simple object types create a new instace and apply the clone injection on it
        return result;
    }
}   

////// Program.cs
public class Program
{

    public static void Main(string[] args)
    {
        Person defaultPerson = getDefaultPerson();
        //Throws an error. I'm not sure where in the pipeline this occurs, but 
        //it seems to happen somewhere other than the Match & SetValue method of 
        //my EntityToViewModel injection
        PersonViewModel pvm = CreateFromEntity(defaultPerson);          

        //Works, but want it more generic & without having to 
        //include hardcoded prefix for every non-simple object on my EF Model
        pvm = CreateFromEntityWorking(defaultPerson);
        Console.ReadLine();
    }

    private static PersonViewModel CreateFromEntity(Person person)
    {
        PersonViewModel pvm = new PersonViewModel();
        pvm.InjectFrom<EntityToViewModel>(person);
        return pvm;
    }

    ///WORKING MAPPING BUT SEEMS TOO HARDCODED
    private static PersonViewModel CreateFromEntityWorking(Person person)
    {
        PersonViewModel personvm = new PersonViewModel();

        //Fill out view model properties with the same name as those on Person
        personvm.InjectFrom(new LoopValueInjection().TargetPrefix("Person"), person);

        if (person != null && person.Address != null)
        {
            //Fill out view model properties for the Address
            personvm.InjectFrom(new LoopValueInjection().TargetPrefix("Address"), person.Address);
        }

        return personvm;
    }       

    public static Person getDefaultPerson()
    {
        Person p = new Person();
        Address a = new Address();
        p.Id = 1;
        p.FirstName = "John";
        p.LastName = "McClain";
        a.City = "New York";
        a.State = "New York";
        a.Zip = "55555";
        a.Id = 2;
        p.Address = a;
        return p;
    }   

}

回答1:


Finally found the examples I needed. Sadly,despite all my searching I've only just now landed on these. That's no one's fault by my own; however, my foot is sore from how much I've been kicking it.

Pertinent URLS:

The documentation (RTFM) http://valueinjecter.codeplex.com/documentation

Flattening Example & Convention http://valueinjecter.codeplex.com/wikipage?title=flattening&referringTitle=Documentation

Unflattening Example & Convention http://valueinjecter.codeplex.com/wikipage?title=unflattening&referringTitle=Documentation

Solution to my issue:

The convention that must be used on the view model is that top level properties are not prefixed by their property name as it would exist on the Entity. So, in my class below where FirstName, LastName, and Id all exist as top level properties on my Person entity, they don't get prefixed in my view model class. The City, State, Zip, and Id for the Person.Address property do get prefixed.

Oddly enough, I thought of this solution while I was trying to implement my own injection. I got one working that went from Entity to ViewModel....just not the other way around.

public class PersonViewModelPrefixed
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Id { get; set; }
    public int AddressId { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
    public string AddressZip { get; set; } 
}

public static void Main(string[] args)
{   
        Person defaultPerson = getDefaultPerson();
        PersonViewModelPrefixed defaultPrefixedVm = getDefaultPrefixedViewModel();

        //flatten - Entity to View Model
        PersonViewModelPrefixed pvm = Flatten(defaultPerson);

        //unflatten - View Model to Entity
        Person person2 = Unflatten(defaultPrefixedVm);  
    Console.ReadLine();
}       

//unflatten - View Model to Entity
private static Person Unflatten(PersonViewModelPrefixed personViewModel)
{
    Person p = new Person();
    p.InjectFrom<UnflatLoopValueInjection>(personViewModel);
    return p;
}

//flatten - Entity to View Model
private static PersonViewModelPrefixed Flatten(Person person)
{
    PersonViewModelPrefixed pvm = new PersonViewModelPrefixed();
    pvm.InjectFrom<FlatLoopValueInjection>(person);
    return pvm;
}

So, given these details, does any one have some suggestions on how to make the Flatten & Unflatten methods more generic? That's what I'll be working on next.



来源:https://stackoverflow.com/questions/12824836/how-do-i-create-a-custom-value-injection-to-map-my-entity-to-my-view-model-atte

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!