我正在寫一個簡單的消息模塊,以便一個進程可以發佈消息,另一個可以訂閱它們。我使用EF/SqlServer作爲進程外通信機制。 「服務器」只是發佈者/訂戶對共同的名稱(可能被稱爲「頻道」)。你如何處理兩個客戶端upserts之間的競賽?
我有以下方法,添加一行到數據庫中代表一個名爲「服務器」
public void AddServer(string name)
{
if (!context.Servers.Any(c => c.Name == name))
{
context.Servers.Add(new Server { Name = name });
}
}
我遇到的問題是,當我在同一時間啓動兩個客戶端,只有一個是應該添加一個新的服務器條目,但是,這不是它的工作方式。實際上,我得到了兩個具有相同名稱的輸入的錯誤結果,並意識到Any()後衛對此並不足夠。
服務器實體使用一個int PK,據說我的倉庫會強制名稱字段的唯一性。我開始認爲這不會起作用。
public class Server
{
public int Id { get; set; }
public string Name { get; set; }
}
這兩個方面,我認爲我能解決這個問題似乎都不太理想:
- 字符串主鍵
- 忽略異常
這是併發的問題,正確的?
如何在這種情況下處理它,我希望兩個客戶端使用相同的名稱調用存儲庫,但在數據庫中只獲得具有該名稱的一行結果?
更新:這裏是庫代碼
namespace MyBus.Data
{
public class Repository : IDisposable
{
private readonly Context context;
private readonly bool autoSave;
public delegate Chain Chain(Action<Repository> action);
public static Chain Command(Action<Repository> action)
{
using (var repo = new Data.Repository(true))
{
action(repo);
}
return new Chain(next => Command(next));
}
public Repository(bool autoSave)
{
this.autoSave = autoSave;
context = new Context();
}
public void Dispose()
{
if (autoSave)
context.SaveChanges();
context.Dispose();
}
public void AddServer(string name)
{
if (!context.Servers.Any(c => c.Name == name))
{
context.Servers.Add(new Server { Name = name });
}
}
public void AddClient(string name, bool isPublisher)
{
if (!context.Clients.Any(c => c.Name == name))
{
context.Clients.Add(new Client
{
Name = name,
ClientType = isPublisher ? ClientType.Publisher : ClientType.Subscriber
});
}
}
public void AddMessageType<T>()
{
var typeName = typeof(T).FullName;
if (!context.MessageTypes.Any(c => c.Name == typeName))
{
context.MessageTypes.Add(new MessageType { Name = typeName });
}
}
public void AddRegistration<T>(string serverName, string clientName)
{
var server = context.Servers.Single(c => c.Name == serverName);
var client = context.Clients.Single(c => c.Name == clientName);
var messageType = context.MessageTypes.Single(c => c.Name == typeof(T).FullName);
if (!context.Registrations.Any(c =>
c.ServerId == server.Id &&
c.ClientId == client.Id &&
c.MessageTypeId == messageType.Id))
{
context.Registrations.Add(new Registration
{
Client = client,
Server = server,
MessageType = messageType
});
}
}
public void AddMessage<T>(T item, out int messageId)
{
var messageType = context.MessageTypes.Single(c => c.Name == typeof(T).FullName);
var serializer = new XmlSerializer(typeof(T));
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
serializer.Serialize(sw, item);
}
var message = new Message
{
MessageType = messageType,
Created = DateTime.UtcNow,
Data = sb.ToString()
};
context.Messages.Add(message);
context.SaveChanges();
messageId = message.Id;
}
public void CreateDeliveries<T>(int messageId, string serverName, string sendingClientName, T item)
{
var messageType = typeof(T).FullName;
var query = from reg in context.Registrations
where reg.Server.Name == serverName &&
reg.Client.ClientType == ClientType.Subscriber &&
reg.MessageType.Name == messageType
select new
{
reg.ClientId
};
var senderClientId = context.Clients.Single(c => c.Name == sendingClientName).Id;
foreach (var reg in query)
{
context.Deliveries.Add(new Delivery
{
SenderClientId = senderClientId,
ReceiverClientId = reg.ClientId,
MessageId = messageId,
Updated = DateTime.UtcNow,
DeliveryStatus = DeliveryStatus.Sent
});
}
}
public List<T> GetDeliveries<T>(string serverName, string clientName, out List<int> messageIds)
{
messageIds = new List<int>();
var messages = new List<T>();
var clientId = context.Clients.Single(c => c.Name == clientName).Id;
var query = from del in context.Deliveries
where del.ReceiverClientId == clientId &&
del.DeliveryStatus == DeliveryStatus.Sent
select new
{
del.Id,
del.Message.Data
};
foreach (var item in query)
{
var serializer = new XmlSerializer(typeof(T));
using (var sr = new StringReader(item.Data))
{
var t = (T)serializer.Deserialize(sr);
messages.Add(t);
messageIds.Add(item.Id);
}
}
return messages;
}
public void ConfirmDelivery(int deliveryId)
{
using (var context = new Context())
{
context.Deliveries.First(c => c.Id == deliveryId).DeliveryStatus = DeliveryStatus.Received;
context.SaveChanges();
}
}
}
}
我認爲您需要使用事務。 – gunr2171
你的兩個客戶是否重新使用'context'的同一個實例? – EkoostikMartin
不,他們是兩個獨立的過程。 –