Jak usunąć wiele wierszy w Entity Framework (bez foreach)


305

Usuwam kilka elementów z tabeli za pomocą Entity Framework. Nie ma klucza obcego / obiektu nadrzędnego, więc nie mogę tego obsłużyć za pomocą OnDeleteCascade.

Teraz robię to:

var widgets = context.Widgets
    .Where(w => w.WidgetId == widgetId);

foreach (Widget widget in widgets)
{
    context.Widgets.DeleteObject(widget);
}
context.SaveChanges();

Działa, ale Foreach mnie wkurza. Używam EF4, ale nie chcę wykonywać SQL. Chcę się tylko upewnić, że niczego nie przegapię - jest tak dobry, jak to możliwe, prawda? Mogę to streścić za pomocą metody rozszerzenia lub pomocnika, ale gdzieś nadal będziemy robić foreach, prawda?


1
Możesz ponownie sprawdzić zaakceptowaną odpowiedź.
Eric J.,

1
Jeśli chcesz pozostać wykonawcą, może zechcesz sprawdzić moją odpowiedź tutaj stackoverflow.com/a/35033286/274589
Adi

Odpowiedzi:


49

Jeśli nie chcesz bezpośrednio uruchamiać SQL, wywoływanie w pętli DeleteObject to najlepsze, co możesz zrobić dzisiaj.

Jednak możesz wykonać SQL i nadal uczynić go całkowicie ogólnym celem za pomocą metody rozszerzenia, używając podejścia, które tu opisuję .

Chociaż odpowiedź była na 3.5. W przypadku wersji 4.0 prawdopodobnie użyłbym nowego interfejsu API ExecuteStoreCommand pod maską, zamiast zejść do StoreConnection.


ExecuteStoreCommand nie jest właściwym sposobem.DeleteAllSubmit działa w Linq na SQL, ale nie w frameworku encji. Chcę tę samą opcję w ramach encji.
Hiral

653

EntityFramework 6 sprawił, że jest to nieco łatwiejsze .RemoveRange().

Przykład:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();

31
Właśnie tego potrzebujemy ... Z wyjątkiem sytuacji, gdy używam go na wystarczająco dużym zasięgu, dostaję wyjątek braku pamięci! Myślałem, że cały sens RemoveRange polegał na przekazaniu przetwarzania do bazy danych, ale najwyraźniej nie.
Samer Adra

to jest WAAAYYYY szybciej niż ustawienie stanu Usunięte dla każdej jednostki!
Jerther

54
Z pewnością ta odpowiedź jest łatwiejsza, ale pod względem wydajności może nie być świetna. Dlaczego? co ten dokładnie robi to samo, co usuwanie go w pętli foreach, najpierw pobiera wszystkie wiersze, a następnie usuwa jeden po drugim, tylko zysk służy do zapisania „DetectChanges zostanie wywołany raz przed usunięciem jakichkolwiek bytów i nie będzie ponownie nazwany” odpocznij to samo, spróbuj użyć narzędzia, aby zobaczyć wygenerowane sql.
Anshul Nigam

6
Aby uzyskać wystarczająco duży zakres, spróbuj czegoś takiego, jak .Take (10000) i zapętlanie aż do RemoveRange (...). Count () == 0.
Eric J.

20
Problem polega na tym, że parametr wejściowy RemoveRange jest IEnumerable, więc aby go usunąć, wylicza wszystkie jednostki i uruchamia 1 zapytanie DELETE na jednostkę.
bubi

74

to jest tak dobre, jak to możliwe, prawda? Mogę to streścić za pomocą metody rozszerzenia lub pomocnika, ale gdzieś nadal będziemy robić foreach, prawda?

Cóż, tak, ale możesz zrobić z tego dwuwarstwową:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();

76
Robisz ToList (), który pokonuje cel. Czym różni się to od oryginalnego rozwiązania?
lahsrah

3
Mam problemy, ponieważ mam metodę Remove tylko w obiekcie kontekstowym.
Pkt

2
To zdecydowanie nie jest odpowiednie rozwiązanie, gdy spodziewany jest milion wierszy (a nawet kilkaset). Jednak jeśli wiemy na pewno, będzie tylko kilka rzędów, to rozwiązanie jest schludne i działa doskonale. Tak, wymagałoby to kilku podróży w obie strony do bazy danych, ale moim zdaniem utracona abstrakcja związana z wywoływaniem SQL bezpośrednio przeważa nad korzyściami.
Yogster,

Entity Framework, jak sama nazwa wskazuje, najlepiej działa z danymi na poziomie encji. Operacje na danych masowych najlepiej wykonywać przy pomocy starych, dobrych przechowywanych procesów. Pod względem wydajności są zdecydowanie najlepszymi opcjami i pokonają wszelkie logiki EF wymagające pętli.
Paceman

72
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}

Ale jak możesz to zrobić z listą identyfikatorów? To rozwiązanie nie radzi sobie zbyt dobrze z „listami”.
JesseNewman19

11
@ JesseNewman19 Jeśli masz już listę identyfikatorów, użyj a WHERE IN ({0}), a następnie drugi argument powinien być String.Join(",", idList).
Langdon,

@ Langdon, który nie będzie działał, ponieważ wyśle ​​polecenie do sql tak: WHERE IN („1, 2, 3”). Następnie baza danych generuje błąd, ponieważ przekazano mu ciąg zamiast listy liczb całkowitych.
JesseNewman19

Chcę wygenerować takie oświadczenie za pomocą LINQ. Najbliższą rzeczą, jaką znalazłem, była lib. EntityFramework.Extended
Jaider

Jeśli używasz String.Join, może być konieczne użycie string.Formati przekazanie już utworzonego ciągu SQL do polecenia. Tak długo, jak na liście znajdują się tylko liczby całkowite, nie ma ryzyka ataku iniekcyjnego. Sprawdź to pytanie: jak mogę przekazać tablicę do komendy wykonywania sklepu?
Andrew

50

Wiem, że jest już dość późno, ale na wypadek, gdyby ktoś potrzebował prostego rozwiązania, fajną rzeczą jest dodanie do niego klauzuli where:

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    string selectSql = db.Set<T>().Where(filter).ToString();
    string fromWhere = selectSql.Substring(selectSql.IndexOf("FROM"));
    string deleteSql = "DELETE [Extent1] " + fromWhere;
    db.Database.ExecuteSqlCommand(deleteSql);
}

Uwaga: właśnie przetestowano z MSSQL2008.

Aktualizacja:

Powyższe rozwiązanie nie będzie działać, gdy EF wygeneruje instrukcję SQL z parametrami , więc oto aktualizacja dla EF5 :

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    var query = db.Set<T>().Where(filter);

    string selectSql = query.ToString();
    string deleteSql = "DELETE [Extent1] " + selectSql.Substring(selectSql.IndexOf("FROM"));

    var internalQuery = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_internalQuery").Select(field => field.GetValue(query)).First();
    var objectQuery = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_objectQuery").Select(field => field.GetValue(internalQuery)).First() as ObjectQuery;
    var parameters = objectQuery.Parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToArray();

    db.Database.ExecuteSqlCommand(deleteSql, parameters);
}

Wymaga trochę refleksji, ale działa dobrze.


Co to jest DbContext? Zakładam, że twój kontekst generowanej automatycznie encji? Nie mam metody o nazwie Ustaw <T>.
Stealth Rabbi

@Stealth: Tak, to twój kontekst danych EF, najpierw używam kodu, ale automatycznie wygenerowany kontekst powinien być taki sam. Niestety na rachunku mis wpisane powinno być Set <T> () (moja firma retricts dostępu do internetu nie mogę wkleić kod, musiał wpisać ręcznie, tak ...), kody zaktualizowany :)
Thanh Nguyen

3
To jedyna odpowiedź, która faktycznie odpowiada na pytanie! Każda inna odpowiedź usuwa każdy element pojedynczo, niewiarygodne.
Rocklan,

To wydaje się najbardziej poprawna odpowiedź. Pozwala na usunięcie w bardzo ogólny sposób i właściwie odciąża pracę do bazy danych, a nie C #.
JesseNewman19

1
Dla wszystkich mniej technicznych programistów, chciałem nieco więcej rozwinąć, jak wdrożyć to doskonałe i ogólne rozwiązanie, ponieważ zaoszczędziłoby mi to kilka minut! Ciąg dalszy w następnym komentarzu ...
jdnew18

30

Dla każdego używającego EF5 można użyć następującej biblioteki rozszerzeń: https://github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);

3
Ma problemy z wydajnością na dużych stołach, które nie nadają się do użycia w mojej sytuacji.
Tomas

@Tomas jakiego rodzaju wydarzenie zauważyłeś? Jak poważny był ten problem i jak duży był stół? Ktoś inny może to potwierdzić?
Anestis Kivranoglou

Jest naprawdę szybki w porównaniu do dostępnych tam alternatyw
Jaider

Nie widzę Delete()funkcji w moich bytach w EF6.
dotNET,

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();jest nowszym sposobem dzięki EntityFramework.Extended
Peter Kerr

11

Nadal wydaje się szalone, że trzeba wyciągać cokolwiek z serwera, aby je usunąć, ale przynajmniej odzyskanie samych identyfikatorów jest o wiele prostsze niż ściąganie pełnych elementów:

var ids = from w in context.Widgets where w.WidgetId == widgetId select w.Id;
context.Widgets.RemoveRange(from id in ids.AsEnumerable() select new Widget { Id = id });

Bądź ostrożny - może to nie powieść się w sprawdzaniu poprawności encji Entity Framework, ponieważ twoje Widgetobiekty pośredniczące mają tylko zainicjowaną Idwłaściwość. Rozwiązaniem jest użycie context.Configuration.ValidateOnSaveEnabled = false(przynajmniej w EF6). To wyłącza sprawdzanie poprawności Entity Framework, ale nadal wykonuje własne sprawdzanie poprawności bazy danych.
Sammy S.,

@SammyS. Nie doświadczyłem tego, więc nie mogę mówić o szczegółach, ale wydaje się dziwne, że EF i tak niepokoiłby się sprawdzaniem poprawności, gdy usuwa wiersz.
Edward Brey,

Masz absolutną rację. Myliłem to deletez podobnym obejściem dla updatetworzenia bytów bez ich ładowania.
Sammy S.,

10

EF 6.1

public void DeleteWhere<TEntity>(Expression<Func<TEntity, bool>> predicate = null) 
where TEntity : class
{
    var dbSet = context.Set<TEntity>();
    if (predicate != null)
        dbSet.RemoveRange(dbSet.Where(predicate));
    else
        dbSet.RemoveRange(dbSet);

    context.SaveChanges();
} 

Stosowanie:

// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");

Or:

// delete all from entity
DeleteWhere<MyEntity>();

7
Jest to w rzeczywistości to samo co db.People.RemoveRange (db.People.Where (x => x.State == "CA")); db.SaveChanges (); Więc nie ma wzrostu wydajności.
ReinierDG

4

W przypadku EF 4.1

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");

1
Działa to, ale głównym celem korzystania z Entity Framework jest obiektowy sposób interakcji z bazą danych. To jest po prostu bezpośrednie uruchomienie zapytania SQL.
Arturo Torres Sánchez

4

Możesz do tego celu użyć bibliotek rozszerzeń, takich jak EntityFramework.Extended lub Z.EntityFramework.Plus.EF6, są dostępne dla EF 5, 6 lub Core. Te biblioteki mają doskonałą wydajność, gdy trzeba je usunąć lub zaktualizować i używają LINQ. Przykład usuwania ( źródło plus ):

ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();

lub ( rozszerzone źródło )

context.Users.Where(u => u.FirstName == "firstname") .Delete();

Używają natywnych instrukcji SQL, więc wydajność jest świetna.


Zapłać 600 $ + za generator pracy luzem sql. Poważnie?
nicolay.anykienko

@ nicolay.anykienko Kiedy z niej korzystałem, ta biblioteka była darmowa, są inne operacje, w których trzeba zapłacić, prawda, nie wiem, czy trzeba zapłacić
UUHHIVS,

3

Najszybszym sposobem na usunięcie jest użycie procedury składowanej. Wolę procedury składowane w projekcie bazy danych niż dynamiczny SQL, ponieważ nazwy będą obsługiwane poprawnie i będą miały błędy kompilatora. Dynamiczny SQL może odnosić się do tabel, które zostały usunięte / zmieniono ich nazwy, powodując błędy w czasie wykonywania.

W tym przykładzie mam dwie tabele List i ListItems. Potrzebuję szybkiego sposobu na usunięcie wszystkich ListItems z danej listy.

CREATE TABLE [act].[Lists]
(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [Name] NVARCHAR(50) NOT NULL
)
GO
CREATE UNIQUE INDEX [IU_Name] ON [act].[Lists] ([Name])
GO
CREATE TABLE [act].[ListItems]
(
    [Id] INT NOT NULL IDENTITY, 
    [ListId] INT NOT NULL, 
    [Item] NVARCHAR(100) NOT NULL, 
    CONSTRAINT PK_ListItems_Id PRIMARY KEY NONCLUSTERED (Id),
    CONSTRAINT [FK_ListItems_Lists] FOREIGN KEY ([ListId]) REFERENCES [act].[Lists]([Id]) ON DELETE CASCADE
)
go
CREATE UNIQUE CLUSTERED INDEX IX_ListItems_Item 
ON [act].[ListItems] ([ListId], [Item]); 
GO

CREATE PROCEDURE [act].[DeleteAllItemsInList]
    @listId int
AS
    DELETE FROM act.ListItems where ListId = @listId
RETURN 0

Teraz interesująca część usuwania elementów i aktualizowania struktury Entity za pomocą rozszerzenia.

public static class ListExtension
{
    public static void DeleteAllListItems(this List list, ActDbContext db)
    {
        if (list.Id > 0)
        {
            var listIdParameter = new SqlParameter("ListId", list.Id);
            db.Database.ExecuteSqlCommand("[act].[DeleteAllItemsInList] @ListId", listIdParameter);
        }
        foreach (var listItem in list.ListItems.ToList())
        {
            db.Entry(listItem).State = EntityState.Detached;
        }
    }
}

Główny kod może teraz go używać jako

[TestMethod]
public void DeleteAllItemsInListAfterSavingToDatabase()
{
    using (var db = new ActDbContext())
    {
        var listName = "TestList";
        // Clean up
        var listInDb = db.Lists.Where(r => r.Name == listName).FirstOrDefault();
        if (listInDb != null)
        {
            db.Lists.Remove(listInDb);
            db.SaveChanges();
        }

        // Test
        var list = new List() { Name = listName };
        list.ListItems.Add(new ListItem() { Item = "Item 1" });
        list.ListItems.Add(new ListItem() { Item = "Item 2" });
        db.Lists.Add(list);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(2, list.ListItems.Count);
        list.DeleteAllListItems(db);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(0, list.ListItems.Count);
    }
}

Dziękujemy za dobry przykład użycia procedury składowanej, a następnie zaimplementowania jej jako rozszerzenia z kodem użycia.
glenn garson

3

Jeśli chcesz usunąć wszystkie wiersze tabeli, możesz wykonać polecenie sql

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

TRUNCATE TABLE (Transact-SQL) Usuwa wszystkie wiersze z tabeli bez rejestrowania poszczególnych usuniętych wierszy. TRUNCATE TABLE jest podobna do instrukcji DELETE bez klauzuli WHERE; jednak TRUNCATE TABLE jest szybszy i zużywa mniej zasobów systemowych i dzienników transakcji.


3
Należy również wspomnieć, że nie można uruchamiać truncate tabletabel, do których odwołuje się ograniczenie KLUCZ OBCY. (Możesz obciąć tabelę, która ma obcy klucz, który się do niej odwołuje.). Dokumentacja MSDN
Internet szerokopasmowy

2

UUHHIVSto bardzo elegancki i szybki sposób na usuwanie partii, ale należy go używać ostrożnie:

  • automatyczne generowanie transakcji: zapytania będą objęte transakcją
  • niezależność kontekstu bazy danych: jego wykonanie nie ma nic wspólnego context.SaveChanges()

Te problemy można obejść, przejmując kontrolę nad transakcją. Poniższy kod ilustruje sposób wsadowego usuwania i zbiorczego wstawiania w sposób transakcyjny:

var repo = DataAccess.EntityRepository;
var existingData = repo.All.Where(x => x.ParentId == parentId);  

TransactionScope scope = null;
try
{
    // this starts the outer transaction 
    using (scope = new TransactionScope(TransactionScopeOption.Required))
    {
        // this starts and commits an inner transaction
        existingData.Delete();

        // var toInsert = ... 

        // this relies on EntityFramework.BulkInsert library
        repo.BulkInsert(toInsert);

        // any other context changes can be performed

        // this starts and commit an inner transaction
        DataAccess.SaveChanges();

        // this commit the outer transaction
        scope.Complete();
    }
}
catch (Exception exc)
{
    // this also rollbacks any pending transactions
    scope?.Dispose();
}

2

Entity Framework Core

3,1 3,0 2,2 2,1 2,0 1,1 1,0

using (YourContext context = new YourContext ())
{
    var widgets = context.Widgets.Where(w => w.WidgetId == widgetId);
    context.Widgets.RemoveRange(widgets);
    context.SaveChanges();
}

Podsumowanie :

Usuwa podaną kolekcję encji z kontekstu leżącego u podstaw zestawu, przy czym każda encja jest wprowadzana w stan Usunięte, tak że zostanie ona usunięta z bazy danych po wywołaniu funkcji SaveChanges.

Uwagi :

Zauważ, że jeśli System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled jest ustawiony na wartość true (co jest ustawieniem domyślnym), to funkcja DetectChanges zostanie wywołana jeden raz przed usunięciem jakichkolwiek elementów i nie będzie ponownie wywoływana. Oznacza to, że w niektórych sytuacjach funkcja RemoveRange może działać znacznie lepiej niż wielokrotne wywoływanie opcji Usuń. Zauważ, że jeśli jakikolwiek byt istnieje w kontekście w stanie Dodane, wówczas ta metoda spowoduje odłączenie go od kontekstu. Jest tak, ponieważ zakłada się, że dodana jednostka nie istnieje w bazie danych, dlatego próba jej usunięcia nie ma sensu.


1

Możesz wykonywać zapytania SQL bezpośrednio w następujący sposób:

    private int DeleteData()
{
    using (var ctx = new MyEntities(this.ConnectionString))
    {
        if (ctx != null)
        {

            //Delete command
            return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");

        }
    }
    return 0;
}

Do wybranych możemy użyć

using (var context = new MyContext()) 
{ 
    var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); 
}

Biorąc pod uwagę, że EF nie obsługuje poprawnie mapowania warunków usuwania, jest to prawdopodobnie najlepszy sposób na wykonanie zadania.
Tony O'Hagan

1

Możesz także użyć metody DeleteAllOnSubmit () , przekazując wyniki do ogólnej listy zamiast do var. W ten sposób twój foreach ogranicza się do jednej linii kodu:

List<Widgets> widgetList = context.Widgets
              .Where(w => w.WidgetId == widgetId).ToList<Widgets>();

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

Prawdopodobnie nadal używa pętli wewnętrznie.


3
Wygląda na to, że nie rozumiesz, co to varjest.
freedomn-m

1

Odpowiedź Thanh działała dla mnie najlepiej. Usunąłem wszystkie moje rekordy podczas jednej podróży do serwera. Miałem problem z wywołaniem metody rozszerzenia, więc pomyślałem, że podzielę się moim (EF 6):

Dodałem metodę rozszerzenia do klasy pomocnika w moim projekcie MVC i zmieniłem nazwę na „RemoveWhere”. Wprowadzam dbContext do moich kontrolerów, ale możesz też zrobić using.

// make a list of items to delete or just use conditionals against fields
var idsToFilter = dbContext.Products
    .Where(p => p.IsExpired)
    .Select(p => p.ProductId)
    .ToList();

// build the expression
Expression<Func<Product, bool>> deleteList = 
    (a) => idsToFilter.Contains(a.ProductId);

// Run the extension method (make sure you have `using namespace` at the top)
dbContext.RemoveWhere(deleteList);

Wygenerowało to pojedynczą instrukcję usuwania dla grupy.


0

EF 6. =>

var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();

0

Najlepsza : in EF6 => .RemoveRange()

Przykład:

db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));

14
Czym to się różni od odpowiedzi Kyle'a?

-1

Zobacz odpowiedź „ulubiony kawałek kodu”, która działa

Oto jak go użyłem:

     // Delete all rows from the WebLog table via the EF database context object
    // using a where clause that returns an IEnumerable typed list WebLog class 
    public IEnumerable<WebLog> DeleteAllWebLogEntries()
    {
        IEnumerable<WebLog> myEntities = context.WebLog.Where(e => e.WebLog_ID > 0);
        context.WebLog.RemoveRange(myEntities);
        context.SaveChanges();

        return myEntities;
    }

1
Czym twoja odpowiedź różni się od odpowiedzi user1308743 ?
Sergey Berezovskiy

Po prostu dzieliłem się działającym przykładem. Cokolwiek mogę zrobić, aby oddać za otrzymaną pomoc.
Brian Quinn

-3

W EF 6.2 działa to doskonale, wysyłając usunięcie bezpośrednio do bazy danych bez uprzedniego ładowania podmiotów:

context.Widgets.Where(predicate).Delete();

Przy ustalonym predykacie jest to dość proste:

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();

A jeśli potrzebujesz predykatu dynamicznego, zapoznaj się z LINQKit (dostępny pakiet Nuget), w moim przypadku coś takiego działa dobrze:

Expression<Func<Widget, bool>> predicate = PredicateBuilder.New<Widget>(x => x.UserID == userID);
if (somePropertyValue != null)
{
    predicate = predicate.And(w => w.SomeProperty == somePropertyValue);
}
context.Widgets.Where(predicate).Delete();

1
W przypadku surowego EF 6.2 nie jest to możliwe. Może używasz Z.EntityFramework.Pluslub coś podobnego? ( entityframework.net/batch-delete )
Sammy S.,

Pierwszy to surowy EF 6.2 i działa znaleźć. Drugim, jak wspomniałem, jest użycie LINQKit.
Vladimir

1
Hmm, nie mogę znaleźć tej metody. Czy możesz sprawdzić, w której klasie i w jakiej przestrzeni nazw znajduje się ta metoda?
Sammy S.,

Po trzecie, ( Delete()metoda z natury nie istnieje).
Suma Brak
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.