In this code, I have an Entity Framework 4 model with a single "Thing" entity that has an Id and a Name(string) column. I would like to ensure that when I call FindOrCreateThing(name) from multiple threads, only one row in the Things table will ever be created with the given name.
Currently, I'm using locks to accomplish this, and it seems to work... but, what are some better ways? How is this common scenario handled in other projects?
Thanks!
class Program
{
private static string[] names = new string[] { "Alpha", "Beta", "Delta", "Gamma", "Zeta" };
static void Main(string[] args)
{
// Multiple threads trying to create things, often with the same name,
// but only allow one thread to actually create the record if it doesn't
// exist.
for (int i = 0; i < 100; i++)
{
Thread thread = new Thread(new ThreadStart(MakeThings));
thread.Start();
}
}
static void MakeThings()
{
try
{
foreach (var name in names)
{
Thing t = FindOrCreateThing(name);
Console.WriteLine("Thing record returned: id={0}; name={1}", t.Id, t.Name);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static object createLock = new object();
private static Thing FindOrCreateThing(string name)
{
using (EFModel context = new EFModel())
{
// Find the record. If it already exists, return it--we're done.
var thing = (from t in context.Things
where t.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)
select t).SingleOrDefault();
if (thing == null)
{
// The record does not exist, so wait for lock.
// This will prevent multiple threads from trying to
// create the same record simultaneously.
lock (createLock)
{
// Single thread is here... check if a thread before us
// has already created the record. (I hate having to do this
// same query twice!)
thing = (from t in context.Things
where t.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)
select t).SingleOrDefault();
if (thing == null)
{
// We're the first thread here, so create the record.
// This should mean that the record is unique in the table.
thing = new Thing { Name = name };
context.Things.AddObject(thing);
context.SaveChanges();
}
}
}
return thing;
}
}
}