How to work with 2 models or more? - c#

I have done some search here and also on the web but either I'm using wrong keywords or maybe most of the examples on MVVM deal with one model only.
I'm having two models in my project (self learning project on MVVM), song model and artist model. So far been able to bind a listview with a collection of information (from song), such that when user clicks on a row on listview information about a song are populated in few textbox controls.
The question I'm facing is that how can I communicate between two models? if we consider a model as a table with its column/fields then I should be able to create a reference to artist model (a foreign key) but what I'm not getting is that how I can retrieve information about an artist when I cilck on his song in the listview?
Long story short, I like to click on a row in listview which showing list of songs and then get its singer/artist pictures, his real name and etc. I'm not following the concept behind how to find related piece of data about an song in artist model.
Any advice will be apprecaited.
this is what I have now:
public class Song
{
string _singerId;
string _singerName;
string _songName;
string _songWriter;
string _genre;
int _songYear;
Artist artistReference;
Then I have:
public class Artist
{
string _artistBirthName;
string _artistNationality;
string _artistImageFile;
DateTime _artistDateOfBirth;
DateTime _artistDateOfDeath;
bool _isArtistAlive;
Thanks.
EDIT:
Here is how I provide the information:
Question is how can I insert Artist reference in Song collection?
Artists = new ObservableCollection<Artist>()
{
new Artist() { ArtistBirthName = "Francis Albert Sinatra", ArtistNickName = "Ol' Blue Eyes", ArtistNationality = "American", ... },
new Artist() { ArtistBirthName = "Elvis Aaron Presley", ArtistNickName = "", ArtistNationality = "American", ... },
new Artist() { ArtistBirthName = "James Paul McCartney", ArtistNickName = "", ArtistNationality = "British", ... },
new Artist() { ArtistBirthName = "Thomas John Woodward", ArtistNickName = "", ArtistNationality = "British", ... }
};
//later read it from xml file or a table.
Songs = new ObservableCollection<Song>()
{
new Song() {ARTIST INFO GOES HERE? HOW?, SingerName = "Fank Sinatra", SongName="Fly me to the Moon", SongWriterName="Bart Howard", Genre="Jazz" ,YearOfRelease= 1980 },
new Song() {SingerName = "Elvis Presley", SongName="Can't Help Falling in Love", SongWriterName="Paul Anka", Genre="Pop", YearOfRelease= 1969},
new Song() {SingerName = "The Beatles", SongName="Let It Be", SongWriterName="John Lennon", Genre="Rock", YearOfRelease= 1970},
new Song() {SingerName = "Tom Jones", SongName="Its Not Unusual", SongWriterName="Les Reed & Gordon Mills", Genre="Pop" , YearOfRelease= 1965}
};

I'm either missing something here or you're just looking for difficulties where there really are none. :) When creating a song object, just pass an artist to it. For example Artist artist1 = new Artist(...); Song song1 = new Song(..., artist1);
You'll of course want to define the constructors first.
EDIT: After your edit :)
You can do something like this:
using System.Linq; // For lambda operations
(...)
Songs = new ObservableCollection<Song>()
{
new Song() {Artist = Artists.FirstOrDefault(x => x.Name == "Francis Albert Sinatra"), SingerName = ...}
(...)
}
The Artists.FirstOrDefault(...) part is a LINQ query. It iterates over the Artists collection and selects the first item in the collection that matches the condition. If it doesn't find a matching item, it then uses a default value, which should be NULL. It would be better to give each artist a unique ID and search by it instead of name, though, as there can be more artists with the same name. Don't hesitate to ask if you have more questions!

Related

How to remove all instances inside a list where a contained list does not contain all values given? help please

So I am trying to set up a searching system, for which I need to go through each 'book' in a list and remove each one that does not match the given 'genres'. each book contains a list of genre ID's.
I used this and I am sure it used to work but maybe it was my imagination...
books.RemoveAll(i => i.genres != null && !genres.All(x => i.genres.Any(y => x == y)));
does anyone know how to implament this feature?
Thank you!
It sounds like you're saying that a book has many genres, and you want to filter a list of books based on a list of genres such that all books in the list have all their genres in the genre list.
This answer is also assuming that the Genre class implements IComparable<Genre>. In this example genres is a List<string>:
public class Book
{
public string Title;
public List<string> Genres;
}
Then for sample data, we can create a list of books and a list of genres:
var genres = new List<string> {"Horror", "Action", "Adventure"};
var books = new List<Book>
{
new Book {Title = "The Shining", Genres = new List<string> {"Horror", "Adventure"}},
new Book {Title = "Sahara", Genres = new List<string> {"Action", "Adventure"}},
new Book {Title = "The Odds", Genres = new List<string> {"Action", "Comedy"}}
};
If that's the case, this should do the trick:
books.RemoveAll(book =>
book.Genres != null &&
book.Genres.Any(genre => !genres.Contains(genre)));
// The last book is removed from the list, because 'genres' doesn't contain "Comedy"

C# - Combine multiple LINQ collections with same properties

Maybe it's late in the night, but I'm stumped here. I'm trying to combine multiple lists with the same properties into one. I thought that LINQ's .UNION would do the trick but I was mistaken. Here's an example of a few of my lists:
LIST1 (report names):
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
LIST2 (song names):
Date Name Title Product
01/01/13 John Time Song
01/05/13 Bob Sorry Song
LIST3 (games names):
Date Name Title Product
12/01/12 Google Bike Race Game
12/05/12 Apple Temple Run Game
My class is very simple. Here's what it looks like:
public class MyClass {
public DateTime Date { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Product { get; set; }
}
In case you're wondering, I used this LINQ query to get one of the above lists:
var finalList = Games
.Select (s => new MyClass {
Date = (System.DateTime) s.Games.Creation_date,
Name = s.Games.Last_name,
Title = string.Format("{0} (Report)", s.Game.Headline),
Product="Report"
})
;
So far, it's pretty easy, but I want to combine all my lists into 1. So, my final list should look like:
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
01/01/13 John Time Song
01/05/13 Bob Sorry Song
12/01/12 Google Bike Race Game
12/05/12 Apple Temple Run Game
I thought that a UNION command would do it:
var newList = List1.Union(List2).Union(List3);
But I'm not getting the desired output.
Date Name Title Product
02/01/13 Steve Hello World Report
02/05/13 Greg Howdy Report
01/01/13 Bob Time Game
01/05/13 John Sorry Song
12/01/12 Google Bike Race Song
12/05/12 Apple Temple Run Game
Any idea on what I'm doing wrong here?
Try:
list1.Concat(list2).Concat(list3);
You don't want to be using Union ( working or not) anyway as it does set union.
You could try using the AddRange command should look something like this
var FullList = list1.AddRange(list2).AddRange(list3);
or the fail safe way whould be
var FullList = list1.Concat(list2).Concat(list3).ToList(); //Personally i would use this
or you also have
var FullList = new[] { list1, list2, list3 }.SelectMany(a => GetAllProducts(a)).ToList();

GridView DataKeyNames of nested collections or custom object

I'm using custom objects to bind to a GridView.
Suppose I have a collection of Car Companies (Audi, Ford, BMW, etc). Each Company object has properties like Id, Name, Country, etc.
Each Company also has a Collection of Cars. Each Car has properties like Id, Model, Year, etc.
I want to bind this info to a GridView.
So I'm retrieving a query of Companies and in first object of each Cars Collection, has data of one car (the object model is like this because in other scenarios I have to list all the cars of a Company).
There is no problem when I bind the Gridview to the Companies collection. I can list each company and each car info I want to show.
The problem arises when I want to set the DataKeyNames. I want to set it with the Id of each Car (not the Id of each company) for comparing cars.
I was trying something like this:
GridViewCompanies.DataSource = companies;
GridViewCompanies.DataKeyNames = new string[] { "Cars[0].Id" };
But it does not work, it says it does not contain a property with the name.
Is there any way to set it?
I don't want to bind the Gridview to a Cars collection instead because if I do it that way, I would miss the info of each Company and I need to use company's properties too.
Many thanks.
Try it:
var carCompanyList = new[]
{
new { Id = 1, Name = "Audi", Cars = new[] { new { Id = 10, Model = "Audi_Model_1" }, new { Id = 12, Model = "Audi_Model_2" } } },
new { Id = 2, Name = "Ford", Cars = new[] { new { Id = 20, Model = "Ford_Model_1" }, new { Id = 22, Model = "Ford_Model_2" } } }
};
var gridViewData = from carCompany in carCompanyList
select new
{
carCompany.Id,
carCompany.Name,
firstModelId = carCompany.Cars == null || carCompany.Cars.Count() == 0 ? 0 : carCompany.Cars.First().Id
};
CarModelGridView.DataSource = gridViewData;
CarModelGridView.DataKeyNames = new [] { "firstModelId" };
CarModelGridView.DataBind();

Sort by most recent date and cluster (group) similar titles

Looking for LINQ needed to sort on a date field but also have similar titles grouped and sorted. Consider something like the following desired ordering:
Title Date
"Some Title 1/3" 2009/1/3 "note1: even this is old title 3/3 causes this group to be 1st"
"Some Title 2/3" 2011/1/31 "note2: dates may not be in sequence with titles"
"Some Title 3/3" 2011/1/1 "note3: this date is most recent between "groups" of titles
"Title XYZ 1of2" 2010/2/1
"Title XYz 2of2" 2010/2/21
I've shown titles varying by some suffix. What if a poster used something like the following for titles?
"1 LINQ Tutorial"
"2 LINQ Tutorial"
"3 LINQ Tutorial"
How would the query recognize these are similar titles?
You don't have to solve everything, a solution for the 1st example is much appreciated.
Thank you.
Addendum #1 20110605
#svick also Title authors typically are not thoughtful to use say 2 digits when their numbering scheme goes beyond 9. for example 01,02...10,11 etc..
Typical patterns I've seen tend to be either prefix or suffix or even buried in such as
1/10 1-10 ...
(1/10) (2/10) ...
1 of 10 2 of 10
Part 1 Part 2 ...
You pointed out a valid pattern as well:
xxxx Tutorial : first session, xxxx Tutorial : second session, ....
If I have a Levenshtein function StringDistance( s1, s2 ) how would I fit into the LINQ query :)
Normal grouping in LINQ (and in SQL, but that's not relevant here) works by selecting some key for every element in the collection. You don't have such key, so I wouldn't use LINQ, but two nested foreaches:
var groups = new List<List<Book>>();
foreach (var book in books)
{
bool found = false;
foreach (var g in groups)
{
if (sameGroup(book.Title, g[0].Title))
{
found = true;
g.Add(book);
break;
}
}
if (!found)
groups.Add(new List<Book> { book });
}
var result = groups.Select(g => g.OrderBy(b => b.Date).ToArray()).ToArray();
This gradually creates a list of groups. Each book is compared with the first one in each group. If it matches, it is added to the group. If no group matched, the book creates a new group. In the end, we sort the results using LINQ with dot notation.
It would be more correct if books were compared with each book in a group, not just the first. But you're may not get completely correct results anyway, so I think this optimization is worth it.
This has time complexity O(N²), so it's probably not the best solution if you had millions of books.
EDIT: To sort the groups, use something like
groups.OrderBy(g => g.Max(b => b.Date))
For ordering by date you should use the OrderBy operator.
Example:
//Assuming your table is called Table in datacontext ctx
var data = from t in ctx.Table
order by t.Date
select t;
For grouping strings after similarity you should consider something like the Hamming distance or the Metaphone algorithm. (Although I do not know any direct implementations of these in .Net).
EDIT: As suggested in the comment by svick, the Levenstein distance may also be considered, as a better alternative to the Hamming distance.
Assuming that your Title and Date fields are contained in class called model consider the following class definition
public class Model
{
public DateTime Date{get;set;}
public string Title{get;set;}
public string Prefix
{get
{
return Title.Substring(0,Title.LastIndexOf(' '));
}
}
}
Alongside Date and Title properties i have created a prefix property with no setter and it is returning us the common prefix using substring. you can use any method of your choice in getter of this property. Rest of job is simple. Consider this Linqpad program
void Main()
{
var model = new List<Model>{new Model{Date = new DateTime(2011,1,3), Title = "Some Title 1/3"},
new Model{Date = new DateTime(2011,1,1), Title = "Some Title 2/3"},
new Model{Date = new DateTime(2011,1,1), Title = "Some Title 3/3"},
new Model{Date = new DateTime(2011,1,31), Title = "Title XYZ 1of2"},
new Model{Date = new DateTime(2011,1,31), Title = "Title XYZ 2of2"}};
var result = model.OrderBy(x => x.Date).GroupBy(x => x.Prefix);
Console.WriteLine(result);
}
Edits >>>
If we put the prefix aside the query itself is not returning what I was after which is: 1) Sort the groups by their most recent date 2) sort by title within clusters. Try the following
var model = new List<Model>{
new Model{Date = new DateTime(2009,1,3), Title = "BTitle 1/3"},
new Model{Date = new DateTime(2011,1,31), Title = "BTitle 2/3"},
new Model{Date = new DateTime(2011,1,1), Title = "BTitle 3/3"},
new Model{Date = new DateTime(2011,1,31), Title = "ATitle XYZ 2of2"},
new Model{Date = new DateTime(2011,1,31), Title = "ATitle XYZ 1of2"}
};
var result = model.OrderBy(x => x.Date).GroupBy(x => x.Prefix);
Console.WriteLine(result);

Column headers in CSV using fileHelpers library?

Is there a built-in field attribute in the FileHelper library which will add a header row in the final generated CSV?
I have Googled and didn't find much info on it. Currently I have this:
DelimitedFileEngine _engine = new DelimitedFileEngine(T);
_engine.WriteStream
(HttpContext.Current.Response.Output, dataSource, int.MaxValue);
It works, but without a header.
I'm thinking of having an attribute like FieldTitleAttribute and using this as a column header.
So, my question is at which point do I check the attribute and insert header columns? Has anyone done something similar before?
I would like to get the headers inserted and use custom text different from the actual field name just by having an attribute on each member of the object:
[FieldTitleAttribute("Custom Title")]
private string Name
and maybe an option to tell the engine to insert the header when it's generated.
So when WriteStream or WriteString is called, the header row will be inserted with custom titles.
I have found a couple of Events for DelimitedFileEngine, but not what's the best way to detect if the current record is the first row and how to insert a row before this.
I know this is an old question, but here is an answer that works for v2.9.9
FileHelperEngine<Person> engine = new FileHelperEngine<Person>();
engine.HeaderText = engine.GetFileHeader();
Here's some code that'll do it: https://gist.github.com/1391429
To use it, you must decorate your fields with [FieldOrder] (a good FileHelpers practice anyway). Usage:
[DelimitedRecord(","), IgnoreFirst(1)]
public class Person
{
// Must specify FieldOrder too
[FieldOrder(1), FieldTitle("Name")]
string name;
[FieldOrder(2), FieldTitle("Age")]
int age;
}
...
var engine = new FileHelperEngine<Person>
{
HeaderText = typeof(Person).GetCsvHeader()
};
...
engine.WriteFile(#"C:\people.csv", people);
But support for this really needs to be added within FileHelpers itself. I can think of a few design questions off the top of my head that would need answering before it could be implemented:
What happens when reading a file? Afaik FileHelpers is currently all based on ordinal column position and ignores column names... but if we now have [FieldHeader] attributes everywhere then should we also try matching properties with column names in the file? Should you throw an exception if they don't match? What happens if the ordinal position doesn't agree with the column name?
When reading as a data table, should you use A) the field name (current design), or B) the source file column name, or C) the FieldTitle attribute?
I don't know if you still need this, but here is the way FileHelper is working :
To include headers of columns, you need to define a string with headers delimited the same way as your file.
For example with '|' as delimiter :
public const string HeaderLine = #"COLUMN1|COLUMN2|COLUMN3|...";
Then, when calling your engine :
DelimitedFileEngine _engine = new DelimitedFileEngine<T> { HeaderText = HeaderLine };
If you don't want to write the headers, just don't set the HeaderText attribute on the engine.
List<MyClass> myList = new List<MyClass>();
FileHelperEngine engine = new FileHelperEngine(typeof(MyClass));
String[] fieldNames = Array.ConvertAll<FieldInfo, String>(typeof(MyClass).GetFields(), delegate(FieldInfo fo) { return fo.Name; });
engine.HeaderText = String.Join(";", fieldNames);
engine.WriteFile(MapPath("MyClass.csv"), myList);
Just to include a more complete example, which would have saved me some time, for version 3.4.1 of the FileHelpers NuGet package....
Given
[DelimitedRecord(",")]
public class Person
{
[FieldCaption("First")]
public string FirstName { get; set; }
[FieldCaption("Last")]
public string LastName { get; set; }
public int Age { get; set; }
}
and this code to create it
static void Main(string[] args)
{
var people = new List<Person>();
people.Add(new Person() { FirstName = "James", LastName = "Bond", Age = 38 });
people.Add(new Person() { FirstName = "George", LastName = "Washington", Age = 43 });
people.Add(new Person() { FirstName = "Robert", LastName = "Redford", Age = 28 });
CreatePeopleFile(people);
}
private static void CreatePeopleFile(List<Person> people)
{
var engine = new FileHelperEngine<Person>();
using (var fs = File.Create(#"c:\temp\people.csv"))
using (var sw = new StreamWriter(fs))
{
engine.HeaderText = engine.GetFileHeader();
engine.WriteStream(sw, people);
sw.Flush();
}
}
You get this
First,Last,Age
James,Bond,38
George,Washington,43
Robert,Redford,28
I found that you can use the FileHelperAsyncEngine to accomplish this. Assuming your data is a list called "output" of type "outputData", then you can write code that looks like this:
FileHelperAsyncEngine outEngine = new FileHelperAsyncEngine(typeof(outputData));
outEngine.HeaderText = "Header1, Header2, Header3";
outEngine.BeginWriteFile(outputfile);
foreach (outputData line in output){
outEngine.WriteNext(line);
}
outEngine.Close();
You can simply use FileHelper's GetFileHeader function from base class
var engine = new FileHelperEngine<ExportType>();
engine.HeaderText = engine.GetFileHeader();
engine.WriteFile(exportFile, exportData);

Categories

Resources