Reputation: 6790
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 }}
}
}
}
}
}
Upvotes: 5
Views: 2482
Reputation: 2844
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];
}
Upvotes: 2
Reputation: 110221
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
Upvotes: 1