Norinot
Norinot

Reputation: 11

How do I remove a line from the list based on the ID of that line?

I've been trying to figure this out for the past few days, but I just can't seem to get it work.

So I have a txt file which has this format:

id;könyvcím;szerző;kiadó;kiadási év;

I am using a structs and a list such as this:

public static List<Books> BooksList = new List<Books>();

public struct Books
{
    public int id;
    public string title;
    public string writer;
    public string publisher;
    public int published_year;
}  

And I'm also putting all these into a List based on the struct like this:

StreamReader booksRead = new StreamReader("konyvek.txt", Encoding.UTF8);
booksRead.ReadLine();
while (!booksRead.EndOfStream)
{
    string[] split = booksRead.ReadLine().Split(';');
    Books inRead = new Books();
    inRead.id = Convert.ToInt32(split[0]);
    inRead.title = split[1];
    inRead.writer = split[2];
    inRead.publisher = split[3];
    inRead.published_year = Convert.ToInt32(split[4]);
    BooksList.Add(inRead);
}
booksRead.Close();

All I want is, for example, to find where the line with ID 2 is, and remove that line from my textfile. I've tried to get the index of the line I want, and remove it like that from my textfile, but it even fails to get the index, I tried using IndexOf, FindIndex and trying to go on a loop. I'm pretty sure my struct is not happy with me for using it like that because I get errors such as this when I run my code:

System.InvalidCastException: 'Unable to cast object of type 'Books' to type 'System.IConvertible'.'

Here is the way I'm trying to get the index of the line I want to remove

Books item = new Books();   
for (int i = 0; i < BooksList.Count; i++)
{
    if (Convert.ToInt32(textBox_id_delete.Text) == item.id)
    {
        RemoveAt = item.id;
    }
}
int index = BooksList.FindIndex(x => Convert.ToInt32(x) == RemoveAt);
MessageBox.Show(Convert.ToString(index));

I'm pretty sure I'm approaching this extremely wrong, and I'd accept any kind of help.

Upvotes: 0

Views: 469

Answers (5)

Genusatplay
Genusatplay

Reputation: 771

When you put books into a list from a file, you can search the book for remove from BooksList.
Delete it and save BooksList into a file.

var removeBook = BookList.FirstOrDefault(book => book.id == removeId);
if (removeBook != null)
{
   BookList.Remove(removeBook);
}

var booksAsString = BookList.Select(book => $"{book.id};{book.title};{book.writer};{book.publisher};{book.published_year}");

File.WriteAllLines("konyvek.txt", booksAsString, Encoding.UTF8);

Upvotes: 0

MikeJ
MikeJ

Reputation: 1369

welcome to SO. I'm going to assume you've got a reason for keeping the data in a text file. As several answers have suggested if you need it in a text file the easiest thing to do is to simply create a new file with the lines you want.

One way to do that is to make use of a interator function to filter the lines. This lets you easily use the .NET File class to do the rest - creating the new file and removing the old if you want to. Often keeping the old file and archiving it can be useful too but anyway, here's a way to filter the lines.

    static void Main(string[] _)
    {
        var filteredLines = FilterOnID(File.ReadAllLines("datafile.txt"), "2");

        File.WriteAllLines("updated.datafile.txt", filteredLines);

        // rename if necessary
        File.Delete("datafile.txt");
        File.Move("updated.datafile.txt", "datafile.txt");
    }

    static IEnumerable<string> FilterOnID(IEnumerable<string> lines, string id)
    {
        foreach (var line in lines)
        {
            var fields = line.Split(';');

            if (fields.Length != 0 || !string.IsNullOrEmpty(fields[0]))
            {
                if (id == fields[0])
                    continue;
            }

            yield return line;
        }
    }

To test I added simple file like so:

1;field1;field2;field3
2;field1;field2;field3
3;field1;field2;field3
4;field1;field2;field3
5;field1;field2;field3
6;field1;field2;field3

And after running you get this:

1;field1;field2;field3
3;field1;field2;field3
4;field1;field2;field3
5;field1;field2;field3
6;field1;field2;field3

Upvotes: 0

Cetin Basoz
Cetin Basoz

Reputation: 23797

LiteDb sample:

private static readonly string dataFile = @"d:\temp\books.litedb";

void Main()
{
    //CreateDb(dataFile); // this step is not needed with LiteDB
    // instead we just simply delete the datafile if it exists 
    // for starting afresh
    // if it exists, delete and create afresh, just for sampling
    // so you can run this same sample over and over if you wish
    if (File.Exists(dataFile)) 
    {
        File.Delete(dataFile);
    }

    SeedSampleData(dataFile);
    // List the current data
    Console.WriteLine("Current Data");
    Console.WriteLine("".PadRight(100, '='));
    ListData(dataFile);
    Console.WriteLine("".PadRight(100, '='));
    DeleteSampleRow(dataFile);
    // List the current data
    Console.WriteLine("After deleting");
    Console.WriteLine("".PadRight(100, '='));
    ListData(dataFile);
    Console.WriteLine("".PadRight(100, '='));
}
void DeleteSampleRow(string dbName)
{
    using (var db = new LiteDatabase(dbName))
    {
        var bookCollection = db.GetCollection<Book>("Books");
        // by ID
        bookCollection.Delete(2);
        // by Title
        bookCollection.DeleteMany(c => c.Title == "Sample Title #5");
        // by Writer
        bookCollection.DeleteMany(c => c.Writer == "Sample Writer #3");
    }
}

void ListData(string dbName)
{
    using (var db = new LiteDatabase(dbName))
    {
        var bookCollection = db.GetCollection<Book>("Books");
        foreach (var book in bookCollection.FindAll())
        {
            Console.WriteLine($"{book.Id},{book.Title},{book.Writer},{book.Publisher},{book.Published_year}");
        }
    }
}

private void SeedSampleData(string dbName)
{
    Random r = new Random();
    var books = new List<Book> {
            new Book {Title="Around the World in Eighty Days",Writer = "Jules Verne",Publisher = "Le Temps, Pierre-Jules Hetzel",Published_year= 1873},
            new Book {Title="A Tale of Two Cities",Writer = "Charles Dickens",Publisher = "Chapman & Hall",Published_year= 1859},
        };
    // add dummy 10 more rows
    books.AddRange(Enumerable.Range(0, 10).Select(i => new Book
    {
        Title = $"Sample Title #{i}",
        Writer = $"Sample Writer #{r.Next(1, 5)}",
        Publisher = $"Sample Publisher #{i}",
        Published_year = r.Next(1980, 2022)
    }));
    using (var db = new LiteDatabase(dbName))
    {
        var bookCollection = db.GetCollection<Book>("Books");
        bookCollection.InsertBulk(books);

        // databases generally use some indexes
        // create the same indexes that we created in SQLite sample
        bookCollection.EnsureIndex(c => c.Id);
        bookCollection.EnsureIndex(c => c.Title);
        bookCollection.EnsureIndex(c => c.Writer);
        bookCollection.EnsureIndex(c => c.Publisher);
    }
}

public class Book
{
    public int Id {get;set;}
    public string Title {get;set;}
    public string Writer {get;set;}
    public string Publisher {get;set;}
    public int Published_year {get;set;}
}

Upvotes: 0

Cetin Basoz
Cetin Basoz

Reputation: 23797

SQLite sample:

private static readonly string dataFile = @"d:\temp\books.s3db";
void Main()
{
    CreateDb(dataFile);
    SeedSampleData(dataFile);
    // List the current data
    Console.WriteLine("Current Data");
    Console.WriteLine("".PadRight(100, '='));
    ListData(dataFile);
    Console.WriteLine("".PadRight(100, '='));
    DeleteSampleRow(dataFile);
    // List the current data
    Console.WriteLine("After deleting");
    Console.WriteLine("".PadRight(100, '='));
    ListData(dataFile);
    Console.WriteLine("".PadRight(100, '='));
}
void DeleteSampleRow(string dbName)
{
    string deleteById = "delete from books where id = @id";
    string deleteByTitle = "delete from books where Title = @title";
    string deleteByWriter = "delete from books where Writer = @writer";


    using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
    using (SQLiteCommand cmdById = new SQLiteCommand(deleteById, cn))
    using (SQLiteCommand cmdByTitle = new SQLiteCommand(deleteByTitle, cn))
    using (SQLiteCommand cmdByWriter = new SQLiteCommand(deleteByWriter, cn))
    {
        cmdById.Parameters.Add("@id", DbType.Int32).Value = 2; // delete the book with id = 2
        cmdByTitle.Parameters.Add("@title", DbType.String).Value = $"Sample Title #5"; // delete all books having title "Sample Title #5"
        cmdByWriter.Parameters.Add("@writer", DbType.String).Value = $"Sample Writer #3"; // delete all books written by "Sample Writer #3"

        cn.Open();
        cmdById.ExecuteNonQuery();
        cmdByTitle.ExecuteNonQuery();
        cmdByWriter.ExecuteNonQuery();
        cn.Close();
    }
}

void ListData(string dbName)
{
    string selectCommand = "select * from books";
    using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
    using (SQLiteCommand cmd = new SQLiteCommand(selectCommand, cn))
    {
        cn.Open();
        var r = cmd.ExecuteReader();
        while (r.Read())
        {
            Console.WriteLine($"{r["id"]},{r["title"]},{r["writer"]},{r["publisher"]},{r["published_year"]}");
        }
        cn.Close();
    }
}

private void CreateDb(string dbName)
{
    if (File.Exists(dbName)) // if it exists, delete and create afresh, just for sampling
    {
        File.Delete(dbName);
    }
    string createTable = @"Create Table books (
            id int primary key not null, 
            title varchar(500) not null,
            writer varchar(100) not null,
            publisher varchar(100) not null,
            published_year int not null
            )";
    using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
    using (SQLiteCommand cmd = new SQLiteCommand(createTable, cn))
    {
        cn.Open();
        cmd.ExecuteNonQuery();
        cn.Close();
    }
}
private void SeedSampleData(string dbName)
{
    string insertCommand = @"insert into books 
        (id, title, writer, publisher, published_year)
        values
        (@id, @title, @writer, @publisher, @year);";
    using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
    using (SQLiteCommand cmd = new SQLiteCommand(insertCommand, cn))
    {
        cmd.Parameters.Add("@id", DbType.Int32);
        cmd.Parameters.Add("@title", DbType.String);
        cmd.Parameters.Add("@writer", DbType.String);
        cmd.Parameters.Add("@publisher", DbType.String);
        cmd.Parameters.Add("@year", DbType.Int32);
        Random r = new Random();
        cn.Open();

        int id = 1;
        using (SQLiteTransaction transaction = cn.BeginTransaction())
        {
            cmd.Parameters["@id"].Value = id++;
            cmd.Parameters["@title"].Value = $"Around the World in Eighty Days";
            cmd.Parameters["@writer"].Value = $"Jules Verne";
            cmd.Parameters["@publisher"].Value = $"Le Temps, Pierre-Jules Hetzel";
            cmd.Parameters["@year"].Value = 1873;
            cmd.ExecuteNonQuery();
            cmd.Parameters["@id"].Value = id++;
            cmd.Parameters["@title"].Value = $"A Tale of Two Cities";
            cmd.Parameters["@writer"].Value = $"Charles Dickens";
            cmd.Parameters["@publisher"].Value = $"Chapman & Hall";
            cmd.Parameters["@year"].Value = 1859;
            cmd.ExecuteNonQuery();


            // add dummy 10 more rows
            for (int i = 0; i < 10; i++)
            {
                cmd.Parameters["@id"].Value = id++;
                cmd.Parameters["@title"].Value = $"Sample Title #{i}";
                cmd.Parameters["@writer"].Value = $"Sample Writer #{r.Next(1, 5)}";
                cmd.Parameters["@publisher"].Value = $"Sample Publisher #{i}";
                cmd.Parameters["@year"].Value = r.Next(1980, 2022);
                cmd.ExecuteNonQuery();
            }
            transaction.Commit();
        }
        // databases generally use some indexes
        new SQLiteCommand(@"Create Index if not exists ixId on books (id);", cn).ExecuteNonQuery();
        new SQLiteCommand(@"Create Index if not exists ixTitle on books (title);", cn).ExecuteNonQuery();
        new SQLiteCommand(@"Create Index if not exists ixWriter on books (writer);", cn).ExecuteNonQuery();
        new SQLiteCommand(@"Create Index if not exists ixPublisher on books (publisher);", cn).ExecuteNonQuery();

        cn.Close();
    }
}

Upvotes: 1

Cetin Basoz
Cetin Basoz

Reputation: 23797

You are doing it completely wrong for a number of reasons.

First, how would you do that the way you are doing:

void Main()
{
    var filename = @"c:\myFolder\mybooklist.txt";
    // read into an enumerable
    var books = File.ReadAllLines(filename)
        .Select(x => x.Split(';'))
        .Select(x => new Book {
            Id = int.TryParse(x[0], out int bookId)?bookId:0,
            Title = x[1],
            Writer = x[2],
            Publisher = x[3],
            Published_year=int.TryParse(x[4], out int year)?year:0
        });
        
        
    // remove the one with id 2
    // and save back
    var otherBooks = books.Where(b => b.Id != 2);

    File.WriteAllLines(filename, otherBooks.Select(b => $"{b.Id};{b.Title};{b.Writer};{b.Publisher};{b.Published_year}"));
}

public struct Book
{
    public int Id;
    public string Title;
    public string Writer;
    public string Publisher;
    public int Published_year;
}

And now what is wrong with this.

  1. A text file is not a database but you are trying to use a text file as a database.
  2. With a text file, you are not actually doing any control here, if the ID is unique or not (there might be N books with the ID 2).
  3. (Side matter) You are using C#, but looks like you are coming from another language and not using the naming conventions at all.

IMHO, instead you should simply use a database, an embedded one for example like LiteDb or Sqlite. If you care to see a sample with LiteDb or Sqlite, let me know.

EDIT: I am adding SQLite and LiteDb samples. In either case, you would need to add Sqlite.Data.Sqlite and LiteDB respectively from Nuget and add using statements.

In case of SQLite, please note that you could use Linq adding some drivers. I directly used the ADO.Net commands and didn't use a Book class for mapping.

LiteDB, being a NoSQL database written in C# for C#, can directly use objects and support Linq out of the box.

Samples show only the surface for both.

Upvotes: 1

Related Questions