EF Core OnDelete Not Available - c#

I have an issue in our system where a process is attempting to replace a record and it causes a FK Exception and fails. We are using Microsoft.EntityFrameworkCore 2.2.2 in a .Net Standard 2.0 library. Most of the code was ported from an existing project that was not .Net Core.
I am trying to add cascade delete to the following model:
modelBuilder.Entity<TermEntity>()
.ToTable("Term", SchemaName)
.HasKey(k => new { k.TermId, k.TenantId });
by adding the following:
modelBuilder.Entity<TermEntity>()
.ToTable("Term", SchemaName)
.HasKey(k => new { k.TermId, k.TenantId });
.OnDelete(DeleteBehavior.Cascade);
but I then get an error C1061 'KeyBuilder' does not contain a definition for OnDelete which makes sense as that is what is returned from HasKey and the OnDelete is on ReferenceCollectionBuilder. None of the methods used above return one of those and all of them (yes I have tried moving it around) give a similar error. The signature of the method this is in looks like:
protected override void OnModelCreating(ModelBuilder modelBuilder)
When I look up some EF Core tutorials I don't see the methods I am using and instead I see HasOne, WithMany etc usually with OnDelete at the end of the chain. I also thought I read that Cascade was the default so not sure why I have the issue in the first place if that is the case.
Can someone point me in the right direction to getting this fixed please.
The TermEntity looks like:
public class TermEntity
{
/// <summary>
/// Gets or sets the term identifier.
/// </summary>
/// <value>
/// The term identifier.
/// </value>
public short TermId { get; set; }
/// <summary>
/// Gets or sets the Tenant identifier.
/// </summary>
/// <value>
/// The Tenant range identifier.
/// </value>
public short TenantId { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public int Value { get; set; }
}
NB: I was asked to provide the class I was using in the example and realised that I had posted the wrong one. TermEntity is the class I am having problems with.

Related

EF6 specify order of tables in Migration

In my database I have a couple tables with multiple PKs, which are each FKs.
Below is the C# definition of one such Entity:
/// <summary>
/// Represents a permission record in the client database.
/// Contains information on which <see cref="User"/> has access to which <see cref="Company"/> and which <see cref="Module"/>.
/// </summary>
public class Permission
{
/// <summary>
/// Gets or sets the unique identifier of the <see cref="User"/> to which the <see cref="Permission"/> is linked.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the <see cref="Company"/> to which the <see cref="Permission"/> is linked.
/// </summary>
public Guid CompanyId { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the <see cref="Module"/> to which the <see cref="Permission"/> is linked.
/// </summary>
public Guid ModuleId { get; set; }
/// <summary>
/// Gets the date and time at which the <see cref="Permission"/> was created.
/// </summary>
public DateTime CreatedAt { get; } = DateTime.UtcNow;
/// <summary>
/// Gets the date and time at which the <see cref="Permission"/> was last updated.
/// </summary>
public DateTime UpdatedAt { get; } = DateTime.UtcNow;
/// <summary>
/// Gets or sets a concurrency token used to prevent conflicting edits in a <see cref="Permission"/> entity.
/// </summary>
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global", Justification = "Used by Entity Framework.")]
public byte[] RowVersion { get; set; } = null!;
/// <summary>
/// Gets or sets the <see cref="User"/> to which the <see cref="Permission"/> is linked through <see cref="UserId"/>.
/// </summary>
public User User { get; set; } = null!;
/// <summary>
/// Gets or sets the <see cref="Company"/> to which the <see cref="Permission"/> is linked through <see cref="CompanyId"/>.
/// </summary>
public Company Company { get; set; } = null!;
/// <summary>
/// Gets or sets the <see cref="Module"/> linked to the <see cref="Permission"/> through <see cref="ModuleId"/>.
/// </summary>
public Module Module { get; set; } = null!;
}
The FluentAPI configuration for the Permission entity is as below:
public class PermissionConfiguration : IEntityTypeConfiguration<Permission>
{
/// <inheritdoc />
public void Configure(EntityTypeBuilder<Permission> builder)
{
builder = builder ?? throw new ArgumentNullException(nameof(builder));
builder.HasKey(permission => new
{
permission.UserId,
permission.CompanyId,
permission.ModuleId,
});
builder.Property(p => p.CreatedAt)
.HasDefaultValueSql("getdate()")
.ValueGeneratedOnAdd()
.IsRequired();
builder.Property(p => p.UpdatedAt)
.HasDefaultValueSql("getdate()")
.ValueGeneratedOnAddOrUpdate()
.IsRequired();
builder.Property(p => p.RowVersion)
.IsRowVersion();
// Configures one-to-many FK relationship with User table.
builder.HasOne(p => p.User)
.WithMany(u => u.Permissions)
.HasForeignKey(p => p.UserId)
.OnDelete(DeleteBehavior.ClientCascade) // deletions are handled by context instead of SQL behaviour.
.IsRequired();
// Configures one-to-many FK relationship with Company table.
builder.HasOne(p => p.Company)
.WithMany(c => c.Permissions)
.HasForeignKey(p => p.CompanyId)
.OnDelete(DeleteBehavior.ClientCascade) // deletions are handled by context instead of SQL behaviour.
.IsRequired();
// Configures one-to-many FK relationship with Module table.
builder.HasOne(p => p.Module)
.WithMany(m => m.Permissions)
.HasForeignKey(p => p.ModuleId)
.OnDelete(DeleteBehavior.ClientCascade) // deletions are handled by context instead of SQL behaviour.
.IsRequired();
}
}
Every time I apply a new migration, Entity Framework crashes on a FK exception.
At first I figured to set the ON DELETE behaviour for the Permission entity to ClientCascade, so that the context would handle deleting the records.
This has however not fixed it, and it seems the real problem is that EF tries to truncate the rows from either the User, Company or Module tables before getting to the Permission table.
I figured this would not be a new problem and the use case does not seem very far fetched to me, however I have not been able to find a solution to this.
Is there a way to specify an order in which tables are truncated in the Down method of migrations?
I would assume instructing EF to first empty the Permission table before the Company, User and Module tables would solve this issue?
Thanks in advance!

Prevent EF Model creation in secondary apps

So I have a drop and recreate setup in my application's core project which is consumed by all of my apps in this solution. My model changes significantly enough to where I need this drop and recreate the database tables each time I recycle my app pool (app is in rapid development right now.) The problem is that if 2+ projects are kicked off at the same time, the drop and recreate steps on each other and many times there are missing tables.
I need a way to prevent, at runtime, the creation of the model based on a condition. I tried:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (condition)
return;
//...
base.OnModelCreating(modelBuilder);
}
but this is not working... the tables are still being created. How can I do this?
Not quite sure about your scenario.
For the development purposes, you can use different schema for your tables.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("applicationX");
}
If this is not possible, you can suffix/prefix table names for your entities.
If you need shared tables (data) by both application, you can compose all the database in any application sharing the same model building code.
Then, of course, you must somehow create "critical section" for your database recreation code (https://github.com/aspnet/EntityFramework/issues/3042), using Mutex or/and combined it e.g. with your own lock synchronization object (table) in your database.
If you need to construct a model (database) dynamically, e.g., use the dependancy injection to build model dynamically at single DB context:
/// <summary>
/// Formalization of entity framework DbContext model creation <see cref="DbContext.OnModelCreating"/>
/// </summary>
/// <remarks>
/// The reason for this explicit interface of the <see cref="DbContext.OnModelCreating"/> is to make compositions of several model builders into one
/// </remarks>
public interface IEntityFrameworkModelBuilder
{
void BuildModel(ModelBuilder modelBuilder);
}
public abstract class DbContextBase : DbContext
{
protected DbContextBase(DbContextOptions options, IEntityFrameworkModelBuilder modelBuilder) : base(options)
{
_modelBuilder = modelBuilder;
}
public virtual IEntityFrameworkModelBuilder ModelBuilder
{
get { return _modelBuilder ?? (_modelBuilder = new DefaultSchemaEntityFrameworkModelBuilder("dbo")); }
protected set { _modelBuilder = value; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
ModelBuilder.BuildModel(modelBuilder);
}
}
Of course, then you can create e.g. composit pattern class for your IEntityFrameworkModelBuilder:
/// <summary>
/// The composition of <see cref="IEntityFrameworkModelBuilder"/> used to construct <see cref="DbContext"/> model creation from several modules (tests).
/// </summary>
/// <seealso cref="DbContextOnModelCreatingAdaptingModelBuilder"/>
public class EntityFrameworkModelBuilderComposition : IEntityFrameworkModelBuilder, IList<IEntityFrameworkModelBuilder>
{
private readonly IList<IEntityFrameworkModelBuilder> _builders;
public EntityFrameworkModelBuilderComposition() : this(new IEntityFrameworkModelBuilder[0])
{}
public EntityFrameworkModelBuilderComposition(params IEntityFrameworkModelBuilder[] builders)
{
_builders = new List<IEntityFrameworkModelBuilder>(builders);
}
/// <summary>
/// Constructor to take list of builders as underlaying list so it is initialized with these builders and sharing the same reference.
/// </summary>
/// <param name="builders"></param>
public EntityFrameworkModelBuilderComposition(IList<IEntityFrameworkModelBuilder> builders)
{
_builders = builders ?? new List<IEntityFrameworkModelBuilder>();
}
public virtual void BuildModel(ModelBuilder modelBuilder)
{
foreach (var b in _builders)
{
b.BuildModel(modelBuilder);
}
}
}
Just adding, what you could find helpful sooner or later:
/// <summary>
/// The <see cref="IEntityFrameworkModelBuilder"/> implementation of a building model on the base of <see cref="DbContext"/> instance calling its <see cref="DbContext.OnModelCreating"/> protected method.
/// </summary>
/// <seealso cref="DbContextOnModelCreatingAdaptingModelBuilder"/>
/// <seealso cref="EntityFrameworkModelBuilderComposition"/>
public class DbContextOnModelCreatingAdaptingModelBuilder : IEntityFrameworkModelBuilder
{
private readonly Func<DbContext> _dbContextFactoryMethod;
public DbContextOnModelCreatingAdaptingModelBuilder(Func<DbContext> dbContextFactoryMethod)
{
_dbContextFactoryMethod = dbContextFactoryMethod;
}
public void BuildModel(ModelBuilder modelBuilder)
{
using (var dbContext = _dbContextFactoryMethod())
{
MethodInfo onModelCreating = dbContext.GetType().GetMethod("OnModelCreating", BindingFlags.NonPublic | BindingFlags.Instance);
onModelCreating.Invoke(dbContext, new object[] {modelBuilder});
}
}
}
Of course, all the mentioned points can/must be combined to get the desired behaviour. But it's not clear, how you evaluate the model differences, what
My model changes significantly enough to where I need this drop and recreate
means exactly regarding
the drop and recreate steps on each other and many times there are missing tables
Sadly, according to Microsoft, there does not seem to be a way to do it: https://github.com/aspnet/EntityFramework/issues/6346

Entity Framework AddOrUpdate

I tried to insert the data into database from Facebook Graph search API by using EF AddOrUpdate. It is weird the following exception happened.
Violation of PRIMARY KEY constraint 'PK_dbo.Posts'. Cannot insert duplicate key in object 'dbo.Posts'. The duplicate key value is (100000544976851_913461492015341).
The statement has been terminated.
The code is very simple:
H2GO db2 = new H2GO();
if (obj != null)
{
ICollection<Post> posts = new Collection<Post>();
foreach (var data in obj.data)
{
var post = new Post
{
ID = data.id,
Tag = tag,
Content = data.message,
User = data.from.name,
Type = 2,
TagDate = Convert.ToDateTime(data.created_time),
};
db2.Posts.AddOrUpdate(p => p.ID, post);
}
db2.SaveChanges();
I see it has been a while, but I actually had this happen when I marked my model incorrectly and had a two part key and the database had only a one part key. So my model said this:
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>
/// The user identifier.
/// </value>
[Key]
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the plan identifier.
/// </summary>
/// <value>
/// The plan identifier.
/// </value>
[Key]
public string PlanId { get; set; }
However, a user can only have one plan at a time, so the database said this:
It was a dumb mistake on my part and one I found quickly, but ... hopefully this helps someone out.

How do I retrieve a single record on Microsoft CRM using c# if I don't know the GUID?

We have a vendor solution here where it uses Microsoft Dynamics CRM as base. The application includes this custom entity which has the following attributes (truncated to only show the relevant bits)
relationship entity (works like a list of values. like values for populating a dropdown list)
relationshipid (guid datatype. primary key)
description (string and sample values would include "staff" or "customer" or "visitor" and etc)
I want to retrieve a single record from the entity. something like:
select * from relationship where description like "staff%"
I know there's a Retrieve function but I need the guid to use that. I want to emulate the SQL above without having to possibly use QueryExpression. I want to get an object of type Entity instead of EntityCollection which is what a QueryExpression will give me.
thanks a lot :)
The SDK doesn't provide a method to return a single entity unless you have its Id. But you can write an extension method to help yourself out.
Here is what I use:
/// <summary>
/// Gets the first entity that matches the query expression. Null is returned if none are found.
/// </summary>
/// <typeparam name="T">The Entity Type.</typeparam>
/// <param name="service">The service.</param>
/// <param name="qe">The query expression.</param>
/// <returns></returns>
public static T GetFirstOrDefault<T>(this IOrganizationService service, QueryExpression qe) where T : Entity
{
qe.First();
return service.RetrieveMultiple(qe).ToEntityList<T>().FirstOrDefault();
}
/// <summary>
/// Converts the entity collection into a list, casting each entity.
/// </summary>
/// <typeparam name="T">The type of Entity</typeparam>
/// <param name="col">The collection to convert</param>
/// <returns></returns>
public static List<T> ToEntityList<T>(this EntityCollection col) where T : Entity
{
return col.Entities.Select(e => e.ToEntity<T>()).ToList();
}
The GetFirstOrDefault ensures that the QE is only going to retrieve one entity. The ToEntityList Casts each entity to the early bound type being returned.
So the call would look like:
var contact = service.GetFirstOrDefault<Contact>(qe);
just check if the EntityCollection contains one element, if true returns the single element
EntityCollection results = service...
if (results.Entities.Count == 1) {
return results.Entities[0];
}
To add to the other answers, even though you will still get an EntityCollection in return to a query expression, you can specify a top count to only retrieve 1 record as an alternative.
QueryExpression qeOpportunity = new QueryExpression();
qeOpportunity.EntityName = "opportunity";
qeOpportunity.ColumnSet = new ColumnSet(new string[] { "sp_shippingmethod_lookup", "description" });
qeOpportunity.TopCount = 1;
// Add other Conditions here with qeOpportunity.Criteria.AddCondition()....
EntityCollection ecOpportunity = service.RetrieveMultiple(qeOpportunity);
if (ecOpportunity.Entities.Count > 0)
{
string description = ecOpportunity.Entities[0].GetAttributeValue<string>("description");
EntityReference myShippingMethod = ecOpportunity.Entities[0].GetAttributeValue<EntityReference>("sp_shippingmethod_lookup");
}

How to generalise access to DbSet<TEntity> members of a DbContext?

I have a DbContext with several of the following type of members:
public DbSet<JobLevel> JobLevels { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Race> Races { get; set; }
public DbSet<Language> Languages { get; set; }
public DbSet<Title> Titles { get; set; }
All these are where T: IdNamePairBase, which has Id and Name members only. I am trying desperately to find a common interface with which to access any of these members, to generalise the following MVC3 controller code into one controller:
public ActionResult Edit(DropDownListModel model, Guid)
{
var dbSet = _dbContext.Countries;
var newItems = model.Items.Where(i => i.IsNew && !i.IsDeleted).Select(i => new { i.Name });
foreach (var item in newItems)
{
if (!string.IsNullOrWhiteSpace(item.Name))
{
var undead = ((IEnumerable<IdNamePairBase>)dbSet).FirstOrDefault(p => p.Name.ToLower() == item.Name.ToLower());
if (undead != null)
{
// Assign new value to update to the new char. case if present.
undead.Name = item.Name;
undead.IsDeleted = false;
_dbContext.SaveChanges();
continue;
}
var newPair = new Country { Name = item.Name };
dbSet.Add(newPair);
_dbContext.SaveChanges();
}
}
return RedirectToAction("Edit", new {listName = model.ListName});
}
How could I go about resolving my problem that right now I need one controller for each of the DbContext members, like the one above is dedicated to DbSet<Country> Countries?
PARTIAL SOLUTION: Along lines similar to GertArnold's answer below, before I knew about the _dbContext.Set<T> all he highlights, I implemented this method on my context class to get sets of a specific type:
public IEnumerable<DbSet<T>> GetDbSetsByType<T>() where T : class
{
//var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance;
var props = GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.Name.StartsWith("DbSet"))
.Where(p => p.PropertyType.GetGenericArguments().All(t => t == typeof(T)));
return props.Select(p => (DbSet<T>)p.GetValue(this, null));
}
Some generalization is possible by using
var dbSet = _dbContext.Set<T>
and putting most of your method in a method with a generics type parameter.
However, there should be a switch somewhere to decide which type should be specified and which type to create, because I think the type is supplied as a property of the model (is it?). So it probably won't really look elegant, but probably be a lot shorter, with DRY-er code.
To add on Gert Arnold's answer, I want to note that there is another method overload on the dbContext that returns a general DbSet from a type object:
var dbSet = dbContext.Set(typeof(T))
If you want to add blind an object, then create the object using the set.Create() method, or if you already have an object created with the "new" keyowrd, you can convert it by using (similar to this answer)
var entity = dbSet.Create();
dbSet.Add(entity);
DbEntityEntry entry = context.Entry(entity);
entry.CurrentValues.SetValues(yourObject);
I've been looking for an answer to this question and I've found that it is easy to do using the Managed Extensibility Framework. There is a quicker way at the bottom of this post, however MEF allows for a much more scalable approach.
MEF allows you to build dynamic access plugins from disparate Assemblies; however it can be used to quickly populate Collections within a single assembly application.In essence, we'll be using it as a safe way of reflecting our assembly back into the class. In order to make this fully functional, I'm also going to implement the Strategy Pattern to the Entity Framework Model.
Add a reference to your project, pointing to System.ComponentModel.Composition. This will give access to the MEF library.
Now, we need to implement the Strategy Pattern. If you don't have an Interfaces folder, create one, and add IEntity.cs, as below.
IEntity.cs
namespace Your.Project.Interfaces
{
/// <summary>
/// Represents an entity used with Entity Framework Code First.
/// </summary>
public interface IEntity
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
int Id { get; set; }
}
}
Now, each of you concrete entities need to implement this Interface:
public class MyEntity : IEntity
{
#region Implementation of IEntity
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public int Id { get; set; }
#endregion
// Other POCO properties...
}
I find that it is best practice, not to create individual interfaces for each entity, unless you're working in a high testing environment. Pragmatically, interfaces should only be used where that level of abstraction is needed; mainly when more than one concrete class will inherit, or when working with an over-enthusiastic Inversion of Control engine. If you have interfaces for everything in your production model, your architecture more than likely, has major flaws. Anyway, enough of the rambling.
Now that we have all of our entities "strategised", we can use MEF to collate them and populate a collection within your context.
Within your context, add a new property:
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
[ImportMany(typeof(DbSet<IEntity>))]
public IEnumerable<DbSet<IEntity>> Sets { get; private set; }
The [ImportMany(typeof(DbSet<IEntity>))] here, allows MEF to populate the collection.
Next, add the corresponding Export attribute to each DbSet within the context:
[Export(typeof(DbSet<IEntity>))]
public DbSet<MyEntity> MyEntities { get; set; }
Each of the Imported and Exported properties is known as a "part". The final piece to the puzzle is to compose those parts. Add the following to your context's constructor:
// Instantiate the Sets list.
Sets = new List<DbSet<IEntity>>();
// Create a new Types catalogue, to hold the exported parts.
var catalogue = new TypeCatalog(typeof (DbSet<IEntity>));
// Create a new Composition Container, to match all the importable and imported parts.
var container = new CompositionContainer(catalogue);
// Compose the exported and imported parts for this class.
container.ComposeParts(this);
Now, with any luck, you should have a dynamically populated list of DbSets, within your context.
I have used this method to allow easy truncating of all tables via an extension method.
/// <summary>
/// Provides extension methods for DbSet objects.
/// </summary>
public static class DbSetEx
{
/// <summary>
/// Truncates the specified set.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="set">The set.</param>
/// <returns>The truncated set.</returns>
public static DbSet<TEntity> Truncate<TEntity>(this DbSet<TEntity> set)
where TEntity : class, IEntity
{
set.ToList().ForEach(p => set.Remove(p));
return set;
}
}
I have added a method to the context to truncate the whole database.
/// <summary>
/// Truncates the database.
/// </summary>
public void TruncateDatabase()
{
Sets.ToList().ForEach(s => s.Truncate());
SaveChanges();
}
EDIT (Overhaul):
The solution above has now been depreciated. Some tweeking as had to be done to get this to work now. To make this work, you need to import the DbSets into a temporary collection of DbSet of type "object", then cast this collection to DbSet of your required interface type. For basic purposes, the IEntity interface will suffice.
#region Dynamic Table List
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
public List<DbSet<IEntity>> Tables { get; private set; }
/// <summary>
/// Gets a dynamically populated list of DbSets within the context.
/// </summary>
/// <value>
/// A dynamically populated list of DbSets within the context.
/// </value>
[ImportMany("Sets", typeof (DbSet<object>), AllowRecomposition = true)]
private List<object> TableObjects { get; set; }
/// <summary>
/// Composes the sets list.
/// </summary>
/// <remarks>
/// To make this work, you need to import the DbSets into a temporary collection of
/// DbSet of type "object", then cast this collection to DbSet of your required
/// interface type. For basic purposes, the IEntity interface will suffice.
/// </remarks>
private void ComposeSetsList()
{
// Instantiate the list of tables.
Tables = new List<DbSet<IEntity>>();
// Instantiate the MEF Import collection.
TableObjects = new List<object>();
// Create a new Types catalogue, to hold the exported parts.
var catalogue = new TypeCatalog(typeof (DbSet<object>));
// Create a new Composition Container, to match all the importable and imported parts.
var container = new CompositionContainer(catalogue);
// Compose the exported and imported parts for this class.
container.ComposeParts(this);
// Safe cast each DbSet<object> to the public list as DbSet<IEntity>.
TableObjects.ForEach(p => Tables.Add(p as DbSet<IEntity>));
}
#endregion
Next, run the CompileSetsList() facade from the constructor (with best practices for Web shown):
public MvcApplicationContext()
{
// Enable verification of transactions for ExecuteSQL functions.
Configuration.EnsureTransactionsForFunctionsAndCommands = true;
// Disable lazy loading.
Configuration.LazyLoadingEnabled = false;
// Enable tracing of SQL queries.
Database.Log = msg => Trace.WriteLine(msg);
// Use MEF to compile a list of all sets within the context.
ComposeSetsList();
}
Then, just decorate your DbSet<>s like this:
/// <summary>
/// Gets or sets the job levels.
/// </summary>
/// <value>
/// The job levels.
/// </value>
[Export("Sets", typeof(DbSet<object>))]
public DbSet<JobLevel> JobLevels { get; set; }
Now it will work properly.

Categories

Resources