简体   繁体   中英

Converting from IEnumerable<IEnumerable<string>> to ICollection<T>

I'm trying to convert an IEnumerable<IEnumerable<string>> to ICollection<Character> but struggling to figure out the right way to do it since the structure of one object is different than the structure of the other.

The reason for conversion is to take a json deserialization and insert that into a database via Entity Framework. The json data isn't normalized and doesn't match the exact structure of the database. In the database I have a movie and a person that have a many-to-many relationship. The bridge table between them is characters . But the json just has a movie object with an array of people objects and each person with an array of the characters they play ( IEnumerable<IEnumerable<string>> ).

Basically, I need to convert each movie's people and their characters to each movie's character and its respective person.

var movie = dataContractMovies.Select(m => new Movie {
    Title = m.Title,
    // not sure how to convert each Person and their characters
    Characters = // Need to take each Person and their Characters in m.Cast and cast to a new Character and a new Person for which the Character belongs to
});

Convert From

public class MovieDataContract {
    [DataMember(Name = "title")]
    public string Title { get; set; }

    [DataMember(Name = "abridged_cast")]
    public virtual IEnumerable<Person> Cast { get; set; }
}

[DataContract]
public class Person {
    [DataMember(Name = "name")]
    public string Name { get; set; }

    [DataMember(Name = "characters")]
    public IEnumerable<string> Characters { get; set; }
} 

Convert To

public partial class Movie {
    public string Title { get; set; }
    public virtual ICollection<Character> Characters { get; set; }

    public Movie() {
        this.Characters = new HashSet<Character>();
    }
}

public partial class Character {
    public string Name { get; set; }
    public virtual Movie Movie { get; set; }
    public virtual Person Person { get; set; }
}

public partial class Person {
    public string Name { get; set; }
    public virtual ICollection<Character> Characters { get; set; }

    public Person() {
        this.Characters = new HashSet<Character>();
    }
}

UPDATE AND FURTHER QUESTION

What if I just added a public IEnumerable<Character> Characters to the MovieDataContract ? Then I could just do something like this (in theory, haven't tested)...

Characters = m.Characters;

So my new MovieDataContract would look like this...

[DataContract]
public class MovieDataContract {
    [DataMember(Name = "title")]
    public string Title { get; set; }

    [DataMember(Name = "abridged_cast")]
    private IEnumerable<Person> _cast { get; set; }

    public IEnumerable<Character> Characters { 
        get{  
            foreach(var person in _cast) {
                foreach(string name in person.characters) {
                    yield return new Character { Name = name, Person = new Person { Name = person.name }}
                }
            }
        } 
    }
}

I'm making a few assumptions for my answer: 1. We're only inserting new movies, and not updating movies already in the DB. 2. We're using the Entity Framework to do the insert. 3. People in the movies being added aren't already in the DB associated with other movies. 4. Every movie has a cast, and every cast member has at least one character.

At the risk of getting thrown off the cool kids bus my solution doesn't use LINQ, although I'm sure it could be modified to do so. Note that with EF you don't need to set both ends of the association to insert it into the database.

// Lookup is necessary because a person might be in more than one movie.
var personLookup = new Dictionary<string,Person>();

foreach (var contractMovie in dataContractMovies)
{
    var movie = new Movie() { Title = contractMovie.Title };

    foreach (var contractPerson in contractMovie.Cast)
    {
        if (!personLookup.ContainsKey(contractPerson.Name))
        {
            personLookup.Add(contractPerson.Name, new Person() { Name = contractPerson.Name });
        }
        var person = personLookup[contractPerson.Name];

        foreach (var contractCharacter in contractPerson.Characters)
        {
            var character = new Character() { Name = contractCharacter.Name, Person = person, Movie = movie };

            dataContext.Characters.Add(character);
        }
    }
 }
 dataContext.SaveChanges();

If the DataContract mapped Movie to Character instead of Person (like you suggested in your comments) you could get away with something more like this:

var personLookup = new Dictionary<string, Person>();
var movie = dataContractMovies.Select(m => new Movie {
    Title = m.Title,
    Characters = m.Characters.Select(c => new Character() { Name = c.Name, Person = LookupPerson(c.Person, personLookup) }).ToList()
});

public Person LookupPerson(string personName, Dictionary<string, Person> personLookup)
{
        if (!personLookup.ContainsKey(personName))
        {
            personLookup.Add(personName, new Person() { Name = personName });
        }
        return personLookup[personName];
}
var flattenedShape =
  from movie in dataContractMovies
  from person in movie.Cast
  from characterName in person.Characters
  select new {Movie = movie, Person = person, CharacterName = characterName};


List<Character> characters = new List<Character>();
Dictionary<MovieDataContract, Movie> movieMap =
  new Dictionary<MovieDataContract, Movie>();
Dictionary<PersonDataContract, Person> personMap =
  new Dictionary<PersonDataContract, Person>();

foreach(var row in flattenedShape)
{
  //have I seen this movie yet?
  if (!movieMap.ContainsKey(row.Movie))
  {
    movieMap.Add(row.Movie, new Movie() { Title = row.Movie.Title });
  }

  //have I seen this person yet?
  if (!personMap.ContainsKey(row.Person))
  {
    personMap.Add(row.Person, new Person() { Name = row.Person.Name });
  }

  //every character is unique.
  Character x = new Character() { Name = row.CharacterName };
  movieMap.Characters.Add(x);
  x.Movie = movieMap[row.Movie];
  personMap.Characters.Add(x);
  x.Person = personMap[row.Person];
  characters.Add(x);
}

//at this point, all the characters are in characters...
// all the movies are in the movieMap.Values...
// and all the persons are in the personMap.Values

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM