Вопрос: Что почитать о том, как создать многопользовательскую программу WPF C#?

Лучший ответ:
Поделюсь прямо готовым примером
1) Надо как то хранить в БД права пользователя
Вводим понятие "роль" - это некое тривиальное право доступа к конкретному разделу (например user_view, user_edit, user_delete). CREATE TABLE `role` (
 `role_title` varchar(255) NOT NULL COMMENT 'Роль',
 `role_title_description` text NOT NULL COMMENT 'Описание роли',
 PRIMARY KEY (`role_title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Роли пользователей';
Роли объединяются в группы (администраторы, модераторы, итд)CREATE TABLE `group` ( 
 `group_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID группы', 
 `name` text NOT NULL COMMENT 'Имя группы',  
  PRIMARY KEY (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Группы пользователей';

CREATE TABLE `xref_group_role` (  
`group_id` int(11) NOT NULL,  
`role_title` varchar(255) NOT NULL,  
PRIMARY KEY (`group_id`,`role_title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Связь групп и ролей';
Ну и пользователи привязаны к группамCREATE TABLE `xref_user_group` (  
`user_id` int(11) NOT NULL,  
`group_id` int(11) NOT NULL,  
PRIMARY KEY (`group_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Связь пользователей и групп'
2) Когда пользователь авторизуется - получаем список его ролейSELECT 
	distinct `role`.`role_title`
FROM 
	`xref_user_group`
JOIN `xref_group_role`
	ON `xref_user_group`.`group_id` = `xref_group_role`.`group_id`
JOIN `role`
	ON `xref_group_role`.`role_title` = `role`.`role_title`
WHERE
	`xref_user_group`.`user_id` = 123
3) В рамках данного примера подразумевается что все роли исключительно "разрешительные". Если у пользователя есть роль - он может совершать соответствующее действие. Нет - не может.
Соответственно в логике приложения, когда пользователь пытается совершить какое то действие - просто проверяем наличие у него роли.
Фуф)UPDСпасибо, большое!
Только не совсем понятно как это должно взаимодействовать например с меню. Например есть меню Правка -> Добавить, Изменить, Удалить. Если у пользователя есть права на добавление и изменение но нет на удаление, как сделать что бы пункт меню "Удалить" был не активен?
Я под десктоп последний раз кодил лет 12 назад на Visual Basic (еще не .net) , но тем не менее:
У вас должен быть код который создает это меню. В этот код вставляем все проверки ролей.
Если кода меню нет (вы собрали меню через конструктор), то нужно написать код который выполняется скажем при загрузке формы, смотрит на роли текущего пользователя и соответственно включает/выключает пункты меню.

Худший ответ:
Напишу не про базу. а про общую часть. Здесь хорошо бы разделить на слои приложение, UI (WPF приложение), веб-сервис, и БД. Соответственно все взаимодействие с БД идет через веб-сервис, и WPF приложение вообще не знает про базу, работая с моделью. На уровне веб-сервиса реализуется авторизация, и определяется набор операций доступных пользователю (например, админ может все, оператор только операции чтения вызывать). Автоматически получаем многопользовательское приложение, к веб-сервису можно будет подключить сколько угодно WPF клиентов. (соответственно не только WPF при необходимости).
В одной из своих реализаций, где набор данных ограничивался до 100 мегабайт, я вообще использовал SQL Compact Edition, запись(редкая в проекте операция) велась в базу, при запуске веб-сервиса все данные помещались в оперативную память из базы и при чтении данных к диску обращений не было.Создание веб-службыЕще примерСоздание прокси на веб-службу
Хотя мне нравится делать прокси вручную, больше гибкости:[WebServiceBinding(Namespace = "SampleWebService")]
    public class SampleWebServiceProxy : SoapHttpClientProtocol
    {
        public SampleWebServiceProxy(string serviceUrl)
        {
            Url = serviceUrl;
            UseDefaultCredentials = true;
        }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest webRequest = base.GetWebRequest(uri);
            ((HttpWebRequest)webRequest).ServicePoint.ConnectionLeaseTimeout = 0;
            return webRequest;
        }

        [SoapDocumentMethod("GetUser",
            Action = "http://domain/GetUser",
            Use = System.Web.Services.Description.SoapBindingUse.Literal,
            ParameterStyle = SoapParameterStyle.Wrapped)]
        [WebMethod(MessageName = "", Description = "")]
        public User GetUser(Guid id)
        {
            return InvokeMethodWithResult("GetUser", id);
        }
		
		[SoapDocumentMethod("UpdateUser",
            Action = "http://domain/UpdateUser",
            Use = System.Web.Services.Description.SoapBindingUse.Literal,
            ParameterStyle = SoapParameterStyle.Wrapped)]
        [WebMethod(MessageName = "", Description = "")]
        public void UpdateUser(User user)
        {
            return InvokeMethod("UpdateUser", user);
        }

        private object InvokeMethodWithResult(string methodName, params object[] args)
        {
            return Invoke(methodName, args ?? new object[] { }).FirstOrDefault();
        }

        private void InvokeMethod(string methodName, params object[] args)
        {
            Invoke(methodName, args ?? new object[] { }).FirstOrDefault();
        }
    }
Собственно все. В комментарии выше показаны примеры для работы с базой. Использование фреймворков тут нецелесообразно, таблиц немного, проще будет вручную замапить на объекты.
Получаем следующую схему: